summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--configure.ac51
-rw-r--r--docs/reference/wocky-docs.sgml21
-rw-r--r--examples/Makefile.am7
-rw-r--r--examples/dump-certificates.c175
-rw-r--r--examples/receive-messages.c34
-rw-r--r--examples/send-message.c29
-rwxr-xr-xtests/Certificates.mk121
-rw-r--r--tests/Makefile.am274
-rw-r--r--tests/certs/badwild-cert.cfg89
-rw-r--r--tests/certs/badwild-cert.pem26
-rw-r--r--tests/certs/badwild-key.pem145
-rw-r--r--tests/certs/ca-0-crl.cfg89
-rw-r--r--tests/certs/ca-0-crl.pem13
l---------tests/certs/cas/8a76ade9.01
l---------tests/certs/cas/ae5bb84e.01
l---------tests/certs/cas/e7df1717.01
-rw-r--r--tests/certs/crl/ca-0-crl.pem13
l---------tests/certs/crl/e7df1717.r01
-rw-r--r--tests/certs/ss-cert.cfg2
-rw-r--r--tests/certs/ss-cert.pem42
-rw-r--r--tests/certs/wild-cert.cfg89
-rw-r--r--tests/certs/wild-cert.pem26
-rw-r--r--tests/certs/wild-key.pem145
-rw-r--r--tests/wocky-connector-test.c205
-rw-r--r--tests/wocky-node-tree-test.c5
-rw-r--r--tests/wocky-porter-test.c1
-rw-r--r--tests/wocky-stanza-test.c70
-rw-r--r--tests/wocky-test-connector-server.c2
-rw-r--r--tests/wocky-test-connector-server.h2
-rw-r--r--tests/wocky-test-helper.c1
-rw-r--r--tests/wocky-xmpp-node-test.c30
-rw-r--r--tests/wocky-xmpp-reader-test.c69
-rw-r--r--wocky/Makefile.am94
-rw-r--r--wocky/wocky-auth-registry.c21
-rw-r--r--wocky/wocky-auth-registry.h8
-rw-r--r--wocky/wocky-c2s-porter.c66
-rw-r--r--wocky/wocky-connector.c138
-rw-r--r--wocky/wocky-connector.h2
-rw-r--r--wocky/wocky-data-form.h10
-rw-r--r--wocky/wocky-debug-internal.h14
-rw-r--r--wocky/wocky-debug.c30
-rw-r--r--wocky/wocky-debug.h1
-rw-r--r--wocky/wocky-google-relay.c325
-rw-r--r--wocky/wocky-google-relay.h48
-rw-r--r--wocky/wocky-jabber-auth.c9
-rw-r--r--wocky/wocky-jingle-content.c1423
-rw-r--r--wocky/wocky-jingle-content.h168
-rw-r--r--wocky/wocky-jingle-factory.c620
-rw-r--r--wocky/wocky-jingle-factory.h93
-rw-r--r--wocky/wocky-jingle-info-internal.h32
-rw-r--r--wocky/wocky-jingle-info.c731
-rw-r--r--wocky/wocky-jingle-info.h124
-rw-r--r--wocky/wocky-jingle-media-rtp.c1591
-rw-r--r--wocky/wocky-jingle-media-rtp.h129
-rw-r--r--wocky/wocky-jingle-session.c2515
-rw-r--r--wocky/wocky-jingle-session.h137
-rw-r--r--wocky/wocky-jingle-transport-google.c642
-rw-r--r--wocky/wocky-jingle-transport-google.h73
-rw-r--r--wocky/wocky-jingle-transport-iceudp.c618
-rw-r--r--wocky/wocky-jingle-transport-iceudp.h69
-rw-r--r--wocky/wocky-jingle-transport-iface.c282
-rw-r--r--wocky/wocky-jingle-transport-iface.h111
-rw-r--r--wocky/wocky-jingle-transport-rawudp.c403
-rw-r--r--wocky/wocky-jingle-transport-rawudp.h69
-rw-r--r--wocky/wocky-jingle-types.h157
-rw-r--r--wocky/wocky-meta-porter.c26
-rw-r--r--wocky/wocky-muc.c187
-rw-r--r--wocky/wocky-muc.h27
-rw-r--r--wocky/wocky-namespaces.h52
-rw-r--r--wocky/wocky-node.c53
-rw-r--r--wocky/wocky-node.h8
-rw-r--r--wocky/wocky-openssl.c232
-rw-r--r--wocky/wocky-porter.c15
-rw-r--r--wocky/wocky-pubsub-helpers.c3
-rw-r--r--wocky/wocky-pubsub-node-protected.h2
-rw-r--r--wocky/wocky-pubsub-node.c6
-rw-r--r--wocky/wocky-pubsub-node.h4
-rw-r--r--wocky/wocky-pubsub-service.h2
-rw-r--r--wocky/wocky-sasl-auth.c9
-rw-r--r--wocky/wocky-sasl-scram.c2
-rw-r--r--wocky/wocky-stanza.c70
-rw-r--r--wocky/wocky-stanza.h2
-rw-r--r--wocky/wocky-tls-connector.c17
-rw-r--r--wocky/wocky-tls-handler.c107
-rw-r--r--wocky/wocky-tls-handler.h4
-rw-r--r--wocky/wocky-tls.c163
-rw-r--r--wocky/wocky-tls.h3
-rw-r--r--wocky/wocky-uninstalled.pc.in1
-rw-r--r--wocky/wocky-utils.c34
-rw-r--r--wocky/wocky-utils.h2
-rw-r--r--wocky/wocky-xmpp-connection.c2
-rw-r--r--wocky/wocky-xmpp-error.c22
-rw-r--r--wocky/wocky-xmpp-error.h22
-rw-r--r--wocky/wocky-xmpp-reader.c63
-rw-r--r--wocky/wocky-xmpp-reader.h8
-rw-r--r--wocky/wocky-xmpp-writer.c2
-rw-r--r--wocky/wocky.h11
-rw-r--r--wocky/wocky.pc.in11
99 files changed, 13110 insertions, 597 deletions
diff --git a/.gitignore b/.gitignore
index 5f396cd..2cf77f8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -75,6 +75,7 @@ m4/lt~obsolete.m4
wocky/*-signals-marshal.*
wocky/*-enumtypes.*
wocky/wocky-uninstalled.pc
+wocky/wocky.pc
tests/sasl-test.db
tests/test-report.xml
@@ -107,6 +108,7 @@ tests/certtool
tests/tardis
# tests/certs/cas/*
+examples/wocky-dump-certificates
examples/wocky-register
examples/wocky-receive-messages
examples/wocky-send-message
diff --git a/configure.ac b/configure.ac
index b770130..75c1c48 100644
--- a/configure.ac
+++ b/configure.ac
@@ -177,6 +177,41 @@ if test x$prefer_stream_ciphers = xyes; then
fi
fi
+
+# -----------------------------------------------------------
+# Make CA certificates path configurable
+# Stolen from GIO's TLS
+# -----------------------------------------------------------
+AC_MSG_CHECKING([location of system Certificate Authority list])
+AC_ARG_WITH(ca-certificates,
+ [AC_HELP_STRING([--with-ca-certificates=@<:@path@:>@],
+ [path to system Certificate Authority list])])
+if test "$with_ca_certificates" = "no"; then
+ AC_MSG_RESULT([disabled])
+else
+ if test -z "$with_ca_certificates"; then
+ for f in /etc/pki/tls/certs/ca-bundle.crt \
+ /etc/ssl/certs/ca-certificates.crt; do
+ if test -f "$f"; then
+ with_ca_certificates="$f"
+ fi
+ done
+ if test -z "$with_ca_certificates"; then
+ AC_MSG_ERROR([could not find. Use --with-ca-certificates=path to set, or --without-ca-certificates to disable])
+ fi
+ fi
+
+ AC_MSG_RESULT($with_ca_certificates)
+ AC_DEFINE_UNQUOTED([GTLS_SYSTEM_CA_CERTIFICATES], ["$with_ca_certificates"], [path to system Certificate Authority list])
+fi
+
+if test -n "$with_ca_certificates"; then
+ if ! test -f "$with_ca_certificates"; then
+ AC_MSG_WARN([Specified certificate authority file '$with_ca_certificates' does not exist])
+ fi
+fi
+
+
GLIB_GENMARSHAL=`$PKG_CONFIG --variable=glib_genmarshal glib-2.0`
AC_SUBST(GLIB_GENMARSHAL)
@@ -215,6 +250,19 @@ PKG_CHECK_MODULES(LIBIPHB, [libiphb >= 0.61.31],
AC_SUBST(LIBIPHB_CFLAGS)
AC_SUBST(LIBIPHB_LIBS)
+AC_ARG_ENABLE(google-relay,
+ AC_HELP_STRING([--disable-google-relay],
+ [disable Google Jingle relay support]),
+ [enable_google_relay=$enableval], [enable_google_relay=yes])
+
+if test x$enable_google_relay = xyes; then
+ AC_DEFINE(ENABLE_GOOGLE_RELAY, [], [Enable Google Jingle relay support])
+
+ dnl Check for libsoup
+ PKG_CHECK_MODULES(SOUP, libsoup-2.4)
+fi
+AM_CONDITIONAL([ENABLE_GOOGLE_RELAY], [test "x$enable_google_relay" = xyes])
+
AC_ARG_ENABLE(coverage, AC_HELP_STRING([--enable-coverage],
[compile with coverage profiling instrumentation (gcc only)]),
[
@@ -243,6 +291,7 @@ GTK_DOC_CHECK([1.17],[--flavour no-tmpl])
AC_OUTPUT( Makefile \
wocky/Makefile \
wocky/wocky-uninstalled.pc \
+ wocky/wocky.pc \
m4/Makefile \
tools/Makefile \
examples/Makefile \
@@ -263,7 +312,9 @@ Configure summary:
Features:
TLS Backend..........: ${with_tls}
Prefer stream ciphers: ${prefer_stream_ciphers}
+ System CA certs......: ${with_ca_certificates}
SASL2 Tests..........: ${HAVE_LIBSASL2}
gtk-doc documentation: ${enable_gtk_doc}
libiphb integration..: ${have_iphb}
+ Google relay support.: ${enable_google_relay}
"
diff --git a/docs/reference/wocky-docs.sgml b/docs/reference/wocky-docs.sgml
index a2ba269..c96d710 100644
--- a/docs/reference/wocky-docs.sgml
+++ b/docs/reference/wocky-docs.sgml
@@ -13,19 +13,17 @@
<title>API Reference</title>
<xi:include href="xml/wocky-auth-handler.xml"/>
- <xi:include href="xml/wocky-auth-registry-enumtypes.xml"/>
<xi:include href="xml/wocky-auth-registry.xml"/>
<xi:include href="xml/wocky-bare-contact.xml"/>
<xi:include href="xml/wocky-c2s-porter.xml"/>
<xi:include href="xml/wocky-caps-cache.xml"/>
- <xi:include href="xml/wocky-connector-enumtypes.xml"/>
<xi:include href="xml/wocky-caps-hash.xml"/>
<xi:include href="xml/wocky-connector.xml"/>
<xi:include href="xml/wocky-contact-factory.xml"/>
<xi:include href="xml/wocky-contact.xml"/>
- <xi:include href="xml/wocky-data-form-enumtypes.xml"/>
<xi:include href="xml/wocky-data-form.xml"/>
<xi:include href="xml/wocky-debug.xml"/>
+ <xi:include href="xml/wocky-enumtypes.xml"/>
<xi:include href="xml/wocky.xml"/>
<xi:include href="xml/wocky-heartbeat-source.xml"/>
<xi:include href="xml/wocky-http-proxy.xml"/>
@@ -34,7 +32,6 @@
<xi:include href="xml/wocky-jabber-auth-digest.xml"/>
<xi:include href="xml/wocky-jabber-auth-password.xml"/>
<xi:include href="xml/wocky-meta-porter.xml"/>
- <xi:include href="xml/wocky-muc-enumtypes.xml"/>
<xi:include href="xml/wocky-muc.xml"/>
<xi:include href="xml/wocky-namespaces.xml"/>
<xi:include href="xml/wocky-node.xml"/>
@@ -43,10 +40,8 @@
<xi:include href="xml/wocky-ping.xml"/>
<xi:include href="xml/wocky-porter.xml"/>
<xi:include href="xml/wocky-pubsub-helpers.xml"/>
- <xi:include href="xml/wocky-pubsub-node-enumtypes.xml"/>
<xi:include href="xml/wocky-pubsub-node.xml"/>
<xi:include href="xml/wocky-pubsub-node-protected.xml"/>
- <xi:include href="xml/wocky-pubsub-service-enumtypes.xml"/>
<xi:include href="xml/wocky-pubsub-service.xml"/>
<xi:include href="xml/wocky-pubsub-service-protected.xml"/>
<xi:include href="xml/wocky-resource-contact.xml"/>
@@ -59,17 +54,25 @@
<xi:include href="xml/wocky-session.xml"/>
<xi:include href="xml/wocky-stanza.xml"/>
<xi:include href="xml/wocky-tls-connector.xml"/>
- <xi:include href="xml/wocky-tls-enumtypes.xml"/>
<xi:include href="xml/wocky-tls.xml"/>
<xi:include href="xml/wocky-tls-handler.xml"/>
<xi:include href="xml/wocky-utils.xml"/>
<xi:include href="xml/wocky-xmpp-connection.xml"/>
- <xi:include href="xml/wocky-xmpp-error-enumtypes.xml"/>
<xi:include href="xml/wocky-xmpp-error.xml"/>
- <xi:include href="xml/wocky-xmpp-reader-enumtypes.xml"/>
<xi:include href="xml/wocky-xmpp-reader.xml"/>
<xi:include href="xml/wocky-xmpp-writer.xml"/>
+ <xi:include href="xml/jingle-content.xml"/>
+ <xi:include href="xml/jingle-factory.xml"/>
+ <xi:include href="xml/jingle-info-internal.xml"/>
+ <xi:include href="xml/jingle-info.xml"/>
+ <xi:include href="xml/jingle-media-rtp.xml"/>
+ <xi:include href="xml/jingle-session.xml"/>
+ <xi:include href="xml/jingle-transport-google.xml"/>
+ <xi:include href="xml/jingle-transport-iceudp.xml"/>
+ <xi:include href="xml/jingle-transport-iface.xml"/>
+ <xi:include href="xml/jingle-transport-rawudp.xml"/>
+ <xi:include href="xml/jingle-types.xml"/>
</chapter>
<chapter id="object-tree">
diff --git a/examples/Makefile.am b/examples/Makefile.am
index 5546f40..6123dbe 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -1,5 +1,9 @@
EXAMPLES =
+if ! USING_OPENSSL
+EXAMPLES += wocky-dump-certificates
+endif
+
EXAMPLES += wocky-send-message
EXAMPLES += wocky-receive-messages
EXAMPLES += wocky-register
@@ -7,6 +11,9 @@ EXAMPLES += wocky-unregister
INCLUDES := -I$(top_builddir)/wocky
+wocky_dump_certificates_SOURCES = dump-certificates.c
+wocky_dump_certificates_DEPENDENCIES = $(top_builddir)/wocky/libwocky.la
+
wocky_send_message_SOURCES = send-message.c
wocky_send_message_DEPENDENCIES = $(top_builddir)/wocky/libwocky.la
diff --git a/examples/dump-certificates.c b/examples/dump-certificates.c
new file mode 100644
index 0000000..de56ebf
--- /dev/null
+++ b/examples/dump-certificates.c
@@ -0,0 +1,175 @@
+/*
+ * dump-certificates.c - Dump all Certificates from TLS Handshake
+ * Copyright (C) 2011 Collabora Ltd.
+ * @author Stef Walter <stefw@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string.h>
+
+#include <glib.h>
+
+#include <gio/gio.h>
+#include <wocky/wocky.h>
+
+#include <gnutls/x509.h>
+
+static GMainLoop *mainloop;
+
+typedef struct {
+ WockyTLSHandler parent;
+} DumpTLSHandler;
+
+typedef struct {
+ WockyTLSHandlerClass parent_class;
+} DumpTLSHandlerClass;
+
+GType dump_tls_handler_get_type (void);
+
+G_DEFINE_TYPE (DumpTLSHandler, dump_tls_handler, WOCKY_TYPE_TLS_HANDLER)
+
+static void
+dump_tls_handler_init (DumpTLSHandler *self)
+{
+
+}
+
+static void
+dump_tls_handler_verify_async (WockyTLSHandler *self,
+ WockyTLSSession *tls_session,
+ const gchar *peername,
+ GStrv extra_identities,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *res;
+ GPtrArray *chain;
+ gnutls_x509_crt_t cert;
+ gnutls_datum_t datum;
+ gchar buffer[1024 * 20];
+ gsize length;
+ guint i;
+
+ chain = wocky_tls_session_get_peers_certificate (tls_session, NULL);
+
+ for (i = 0; i < chain->len; i++)
+ {
+ GArray *cert_data = g_ptr_array_index (chain, i);
+ datum.data = (gpointer)cert_data->data;
+ datum.size = cert_data->len;
+
+ if (gnutls_x509_crt_init (&cert) < 0)
+ g_assert_not_reached ();
+ if (gnutls_x509_crt_import (cert, &datum, GNUTLS_X509_FMT_DER) < 0)
+ {
+ g_warning ("couldn't parse certificate %u in chain", i);
+ gnutls_x509_crt_deinit (cert);
+ continue;
+ }
+
+ length = sizeof (buffer);
+ gnutls_x509_crt_get_dn (cert, buffer, &length);
+ g_print ("Subject: %.*s\n", (gint) length, buffer);
+
+ length = sizeof (buffer);
+ gnutls_x509_crt_get_issuer_dn (cert, buffer, &length);
+ g_print ("Issuer: %.*s\n", (gint) length, buffer);
+
+ length = sizeof (buffer);
+ if (gnutls_x509_crt_export (cert, GNUTLS_X509_FMT_PEM, buffer, &length) < 0)
+ {
+ g_warning ("couldn't export certificate %u in chain", i);
+ gnutls_x509_crt_deinit (cert);
+ continue;
+ }
+ g_print ("%.*s\n", (gint) length, buffer);
+
+ gnutls_x509_crt_deinit (cert);
+ }
+
+ g_ptr_array_free (chain, TRUE);
+
+ res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ dump_tls_handler_verify_async);
+ g_simple_async_result_complete_in_idle (res);
+ g_object_unref (res);
+}
+
+static gboolean
+dump_tls_handler_verify_finish (WockyTLSHandler *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+ error);
+}
+
+static void
+dump_tls_handler_class_init (DumpTLSHandlerClass *klass)
+{
+ WockyTLSHandlerClass *handler_class = WOCKY_TLS_HANDLER_CLASS (klass);
+ handler_class->verify_async_func = dump_tls_handler_verify_async;
+ handler_class->verify_finish_func = dump_tls_handler_verify_finish;
+}
+
+static void
+connected_cb (
+ GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_main_loop_quit (mainloop);
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ char *jid, *password;
+ WockyConnector *connector;
+ WockyTLSHandler *handler;
+
+ g_type_init ();
+ wocky_init ();
+
+ if (argc != 2)
+ {
+ g_printerr ("Usage: %s <jid>\n", argv[0]);
+ return -1;
+ }
+
+ jid = argv[1];
+ /* This example doesn't use your real password because it does not actually
+ * validate certificates: it just dumps them then declares them valid.
+ */
+ password = "not a chance";
+
+ mainloop = g_main_loop_new (NULL, FALSE);
+ handler = g_object_new (dump_tls_handler_get_type (), NULL);
+ connector = wocky_connector_new (jid, password, NULL, NULL, handler);
+ wocky_connector_connect_async (connector, NULL, connected_cb, NULL);
+
+ g_main_loop_run (mainloop);
+
+ g_object_unref (connector);
+ g_main_loop_unref (mainloop);
+ return 0;
+}
diff --git a/examples/receive-messages.c b/examples/receive-messages.c
index a9cd122..1d6e392 100644
--- a/examples/receive-messages.c
+++ b/examples/receive-messages.c
@@ -3,7 +3,6 @@
#endif
#include <stdio.h>
-#include <stdlib.h>
#include <string.h>
@@ -166,32 +165,59 @@ connected_cb (
}
}
+static gboolean ignore_ssl_errors = FALSE;
+static gchar *resource = NULL;
+
+static GOptionEntry entries[] =
+{
+ { "ignore-ssl-errors", 0, 0, G_OPTION_ARG_NONE, &ignore_ssl_errors,
+ "Continue connecting, even if the server provides an invalid SSL certificate",
+ NULL },
+ { "resource", 'r', 0, G_OPTION_ARG_STRING, &resource,
+ "Connect as a specific resource (default: let the server decide)",
+ "RESOURCE" },
+ { NULL }
+};
+
int
main (int argc,
char **argv)
{
+ GError *error = NULL;
+ GOptionContext *context;
char *jid, *password;
+ WockyTLSHandler *tls_handler = NULL;
WockyConnector *connector;
g_type_init ();
wocky_init ();
- if (argc != 3)
+ context = g_option_context_new ("<jid> <password> - signs in as <jid> and prints incoming messages");
+ g_option_context_add_main_entries (context, entries, NULL);
+ if (!g_option_context_parse (context, &argc, &argv, &error) ||
+ argc != 3)
{
- printf ("Usage: %s <jid> <password>\n", argv[0]);
+ if (error != NULL)
+ printf ("option parsing failed: %s\n", error->message);
+
+ printf ("%s", g_option_context_get_help (context, FALSE, NULL));
return -1;
}
jid = argv[1];
password = argv[2];
+ if (ignore_ssl_errors)
+ tls_handler = wocky_tls_handler_new (TRUE);
+
mainloop = g_main_loop_new (NULL, FALSE);
- connector = wocky_connector_new (jid, password, NULL, NULL, NULL);
+ connector = wocky_connector_new (jid, password, resource, NULL, tls_handler);
wocky_connector_connect_async (connector, NULL, connected_cb, NULL);
g_main_loop_run (mainloop);
g_object_unref (connector);
+ g_clear_object (&tls_handler);
g_main_loop_unref (mainloop);
return 0;
}
diff --git a/examples/send-message.c b/examples/send-message.c
index 109cbdb..96800c1 100644
--- a/examples/send-message.c
+++ b/examples/send-message.c
@@ -105,19 +105,38 @@ connected_cb (
}
}
+static gboolean ignore_ssl_errors = FALSE;
+
+static GOptionEntry entries[] =
+{
+ { "ignore-ssl-errors", 0, 0, G_OPTION_ARG_NONE, &ignore_ssl_errors,
+ "Continue connecting, even if the server provides an invalid SSL certificate",
+ NULL },
+ { NULL }
+};
+
int
main (int argc,
char **argv)
{
+ GError *error = NULL;
+ GOptionContext *context;
char *jid, *password;
+ WockyTLSHandler *tls_handler = NULL;
WockyConnector *connector;
g_type_init ();
wocky_init ();
- if (argc != 5)
+ context = g_option_context_new ("<jid> <password> <receipient> <message> - signs in as <jid> and sends <message> to <recipient>");
+ g_option_context_add_main_entries (context, entries, NULL);
+ if (!g_option_context_parse (context, &argc, &argv, &error) ||
+ argc != 5)
{
- printf ("Usage: %s <jid> <password> <recipient> <message>\n", argv[0]);
+ if (error != NULL)
+ printf ("option parsing failed: %s\n", error->message);
+
+ printf ("%s", g_option_context_get_help (context, FALSE, NULL));
return -1;
}
@@ -126,13 +145,17 @@ main (int argc,
recipient = argv[3];
message = argv[4];
+ if (ignore_ssl_errors)
+ tls_handler = wocky_tls_handler_new (TRUE);
+
mainloop = g_main_loop_new (NULL, FALSE);
- connector = wocky_connector_new (jid, password, NULL, NULL, NULL);
+ connector = wocky_connector_new (jid, password, NULL, NULL, tls_handler);
wocky_connector_connect_async (connector, NULL, connected_cb, NULL);
g_main_loop_run (mainloop);
g_object_unref (connector);
+ g_clear_object (&tls_handler);
g_main_loop_unref (mainloop);
return 0;
}
diff --git a/tests/Certificates.mk b/tests/Certificates.mk
new file mode 100755
index 0000000..ec0fc0c
--- /dev/null
+++ b/tests/Certificates.mk
@@ -0,0 +1,121 @@
+#!/usr/bin/make -f
+
+CERT_DIR := ./certs
+CA_KEY := $(CERT_DIR)/ca-0-key.pem
+CA_DIR := $(CERT_DIR)/cas
+CRL_DIR := $(CERT_DIR)/crl
+CERTTOOL := $(shell which certtool)
+TARDIS := $(shell which datefudge)
+GENKEY := $(CERTTOOL) --generate-privkey --outfile
+CA_CERT := $(CERT_DIR)/ca-0-cert.pem
+CA_CERT2 := $(CERT_DIR)/ca-1-cert.pem
+CA_CERT3 := $(CERT_DIR)/ca-2-cert.pem
+CRTS := new exp ca-0 ca-1 ca-2 rev ss tls unknown wild badwild
+CERTS := $(foreach name,$(CRTS),certs/$(name)-cert.pem)
+KEYS := $(foreach name,$(CRTS),certs/$(name)-key.pem)
+REV_CERT := $(filter %rev-cert.pem, $(CERTS))
+CRL := $(CERT_DIR)/ca-0-crl.pem
+CAS := $(CA_CERT) $(CA_CERT2) $(CA_CERT3)
+DAY := 86400
+YEAR := 365 * $(DAY)
+TIMEWARP := 28 * $(YEAR)
+FUTURE := $(shell date +'%Y-%m-%d %H:%M' -d @$$(($$(date +%s) + $(TIMEWARP))))
+PAST := $(shell date +'%Y-%m-%d %H:%M' -d @$$(($$(date +%s) - $(DAY) * 7)))
+FILTER_BITS := grep -v '^[ ]\+\([a-f0-9]\{2\}:\)\+[a-f0-9]\{2\}$$'
+check_bin = $(or $(shell test -x "$(1)" && echo Y), $(error Need $(2) to $(3)))
+
+.PHONY: certs targets
+.PRECIOUS: $(KEYS) $(CAS)
+
+help:
+ @echo "-----------------------------------------------------------"
+ @echo ./Certificates.mk certs
+ @echo Should recreate any missing certificates, crls or CAs
+ @echo Removing a certificate and recreating it will also
+ @echo recreate a private key for it if one is missing.
+ @echo Each certificate has a .cfg file that determines it contents,
+ @echo if you need to add or alter a certificate, start with its cfg
+ @echo "-----------------------------------------------------------"
+ @echo Known certificates:
+ @echo "[ $(CERTS) ]"
+
+certs: $(CERTS) $(CRL) $(CAS)
+certs: $(CRL_DIR)/ca-0-crl.pem
+certs: $(foreach x,0 1 2,$(CA_DIR)/ca-$(x)-cert.pem)
+
+# x509 certificates:
+%-key.pem:
+ @echo $(call check_bin,$(CERTTOOL),certtool,rebuild $@)
+ $(GENKEY) $@
+
+%/ss-cert.pem: %/ss-key.pem %/ss-cert.cfg
+ @echo $(call check_bin,$(CERTTOOL),certtool,rebuild $@)
+ $(CERTTOOL) --generate-self-signed \
+ --load-privkey $< \
+ --template $(basename $@).cfg \
+ --outfile $@ 2>&1 | $(FILTER_BITS)
+
+%/unknown-ca-cert.pem: %/unknown-ca-key.pem %/unknown-ca-cert.cfg
+ @echo $(call check_bin,$(CERTTOOL),certtool,rebuild $@)
+ $(CERTTOOL) --generate-self-signed \
+ --load-privkey $< \
+ --template $(basename $@).cfg \
+ --outfile $@ 2>&1 | $(FILTER_BITS)
+
+certs/ca-%-cert.pem $(CERT_DIR)/ca-%-cert.pem: $(CERT_DIR)/ca-%-key.pem $(CERT_DIR)/ca-%-cert.cfg
+ @echo $(call check_bin,$(CERTTOOL),certtool,rebuild $@)
+ $(CERTTOOL) --generate-self-signed \
+ --load-privkey $< \
+ --template $(basename $@).cfg \
+ --outfile $@ 2>&1 | $(FILTER_BITS)
+
+%/rev-cert.pem: export CERTCMD = $(CERTTOOL)
+%/tls-cert.pem: export CERTCMD = $(CERTTOOL)
+%/new-cert.pem: export CERTCMD = $(TARDIS) "$(FUTURE)" $(CERTTOOL)
+%/exp-cert.pem: export CERTCMD = $(TARDIS) "$(PAST)" $(CERTTOOL)
+%/wild-cert.pem: export CERTCMD = $(CERTTOOL)
+%/badwild-cert.pem: export CERTCMD = $(CERTTOOL)
+
+$(NEW_CERT) $(EXP_CERT) certs/exp-cert.pem certs/new-cert.pem: NEED_TIME = 1
+
+%/unknown-cert.pem: export CERTCMD = $(CERTTOOL)
+%/unknown-cert.pem: export CA_CERT = $*/unknown-ca-cert.pem
+%/unknown-cert.pem: export CA_KEY = $*/unknown-ca-key.pem
+
+%/unknown-cert.pem: %/unknown-key.pem %/unknown-cert.cfg %/unknown-ca-cert.pem
+ @echo $(call check_bin,$(CERTTOOL),certtool,rebuild $@)
+ $(CERTTOOL) --generate-certificate \
+ --load-ca-certificate $*/unknown-ca-cert.pem \
+ --load-ca-privkey $*/unknown-ca-key.pem \
+ --load-privkey $*/unknown-key.pem \
+ --template $*/unknown-cert.cfg \
+ --outfile $@ 2>&1 | $(FILTER_BITS)
+
+%-cert.pem: %-key.pem %-cert.cfg $(CA_CERT)
+ @echo "CERTIFICATE $@ ($(CERTCMD)): $^"
+ @echo $(call check_bin,$(CERTTOOL),certtool,rebuild $@)
+ @echo $(if $(NEED_TIME),$(call check_bin,$(TARDIS),datefudge,rebuild $@))
+ $(CERTCMD) --generate-certificate \
+ --load-ca-certificate $(CA_CERT) \
+ --load-ca-privkey $(CA_KEY) \
+ --load-privkey $< \
+ --template $*-cert.cfg \
+ --outfile $@ 2>&1 | $(FILTER_BITS)
+
+$(CRL): $(REV_CERT) $(CA_CERT) $(CA_KEY)
+ @echo $(call check_bin,$(CERTTOOL),certtool,rebuild $@)
+ $(CERTTOOL) --generate-crl \
+ --template $(basename $@).cfg \
+ --load-ca-privkey $(CA_KEY) \
+ --load-ca-certificate $(CA_CERT) \
+ --load-certificate $(REV_CERT) \
+ --outfile $@ 2>&1 | $(FILTER_BITS)
+
+$(CA_DIR) $(CRL_DIR):
+ @mkdir -p $@
+
+$(CA_DIR)/%.pem: $(CERT_DIR)/%.pem
+ @cp -av $< $@
+
+$(CRL_DIR)/%.pem: $(CERT_DIR)/%.pem
+ @cp -av $< $@
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 1540352..c4112cb 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -15,7 +15,12 @@ NEW_KEY := $(CERT_DIR)/new-key.pem
NEW_CERT := $(CERT_DIR)/new-cert.pem
TLS_KEY := $(CERT_DIR)/tls-key.pem
TLS_CERT := $(CERT_DIR)/tls-cert.pem
+WILD_KEY := $(CERT_DIR)/wild-key.pem
+WILD_CERT:= $(CERT_DIR)/wild-cert.pem
+BADWILD_KEY := $(CERT_DIR)/badwild-key.pem
+BADWILD_CERT := $(CERT_DIR)/badwild-cert.pem
CA_DIR := $(CERT_DIR)/cas
+CRL_DIR := $(CERT_DIR)/crl
UNKNOWN_KEY := $(CERT_DIR)/unknown-key.pem
UNKNOWN_CERT := $(CERT_DIR)/unknown-cert.pem
LIBWOCKY := $(top_builddir)/wocky/libwocky.la
@@ -34,100 +39,104 @@ TLSDEFS := -DTLS_CA_KEY_FILE='"$(CA_KEY)"' \
-DTLS_UNKNOWN_CRT_FILE='"$(UNKNOWN_CERT)"' \
-DTLS_SERVER_KEY_FILE='"$(TLS_KEY)"' \
-DTLS_SERVER_CRT_FILE='"$(TLS_CERT)"'\
+ -DTLS_WILD_KEY_FILE='"$(WILD_KEY)"' \
+ -DTLS_WILD_CRT_FILE='"$(WILD_CERT)"' \
+ -DTLS_BADWILD_CRT_FILE='"$(BADWILD_CERT)"' \
+ -DTLS_BADWILD_KEY_FILE='"$(BADWILD_KEY)"' \
+ -DTLS_CRL_DIR='"$(CRL_DIR)"' \
-DTLS_CA_DIR='"$(CA_DIR)"'
############################################################################
-TEST_PROGS = wocky-xmpp-reader-test \
- wocky-xmpp-readwrite-test \
- wocky-xmpp-connection-test \
- wocky-porter-test \
- wocky-loopback-test \
- wocky-xmpp-node-test \
- wocky-node-tree-test \
- wocky-stanza-test \
- wocky-roster-test \
+TEST_PROGS = \
wocky-bare-contact-test \
- wocky-resource-contact-test \
+ wocky-caps-hash-test \
+ wocky-connector-test \
wocky-contact-factory-test \
- wocky-session-test \
- wocky-pep-service-test \
- wocky-tls-test \
wocky-data-form-test \
- wocky-pubsub-service-test \
- wocky-pubsub-node-test \
- wocky-connector-test \
- wocky-ping-test \
wocky-jid-validation-test \
+ wocky-loopback-test \
+ wocky-node-tree-test \
+ wocky-pep-service-test \
+ wocky-ping-test \
+ wocky-porter-test \
+ wocky-pubsub-node-test \
+ wocky-pubsub-service-test \
+ wocky-resource-contact-test \
+ wocky-roster-test \
wocky-sasl-utils-test \
- wocky-utils-test \
wocky-scram-sha1-test \
- wocky-caps-hash-test
+ wocky-session-test \
+ wocky-stanza-test \
+ wocky-tls-test \
+ wocky-utils-test \
+ wocky-xmpp-connection-test \
+ wocky-xmpp-node-test \
+ wocky-xmpp-reader-test \
+ wocky-xmpp-readwrite-test \
+ $(NULL)
noinst_PROGRAMS =
-wocky_tls_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_tls_test_SOURCES = \
- wocky-tls-test.c \
- wocky-test-helper.c wocky-test-helper.h \
- wocky-test-stream.c wocky-test-stream.h
-wocky_tls_test_CFLAGS = $(AM_CFLAGS) $(TLSDEFS)
+if HAVE_LIBSASL2
+ TEST_PROGS += wocky-test-sasl-auth
+ noinst_PROGRAMS += wocky-dummy-xmpp-server
+endif
-wocky_xmpp_connection_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_xmpp_connection_test_SOURCES = \
- wocky-xmpp-connection-test.c \
- wocky-test-helper.c wocky-test-helper.h \
- wocky-test-stream.c wocky-test-stream.h
+if HAVE_GIO_PROXY
+ TEST_PROGS += wocky-http-proxy-test
+endif
-wocky_xmpp_readwrite_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_xmpp_readwrite_test_SOURCES = \
- wocky-test-stream.c wocky-test-stream.h \
+wocky_bare_contact_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_bare_contact_test_SOURCES = \
wocky-test-helper.c wocky-test-helper.h \
- wocky-xmpp-readwrite-test.c
+ wocky-test-stream.c wocky-test-stream.h \
+ wocky-bare-contact-test.c
-wocky_xmpp_reader_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_xmpp_reader_test_SOURCES = \
+wocky_caps_hash_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_caps_hash_test_SOURCES = wocky-caps-hash-test.c \
wocky-test-helper.c wocky-test-helper.h \
- wocky-test-stream.c wocky-test-stream.h \
- wocky-xmpp-reader-test.c
+ wocky-test-stream.c wocky-test-stream.h
wocky_connector_test_DEPENDENCIES = $(CA_DIR) certs $(LIBWOCKY)
-
wocky_connector_test_SOURCES = \
- wocky-connector-test.c \
+ wocky-connector-test.c \
wocky-test-sasl-auth-server.c \
wocky-test-sasl-auth-server.h \
wocky-test-connector-server.c \
wocky-test-connector-server.h \
wocky-test-helper.c wocky-test-helper.h \
- wocky-test-stream.c wocky-test-stream.h \
- test-resolver.h \
- test-resolver.c
+ wocky-test-stream.c wocky-test-stream.h \
+ test-resolver.c test-resolver.h
+
wocky_connector_test_LDADD = $(LDADD) @LIBSASL2_LIBS@
wocky_connector_test_CFLAGS = $(AM_CFLAGS) @LIBSASL2_CFLAGS@ $(TLSDEFS)
-if HAVE_LIBSASL2
- TEST_PROGS += wocky-test-sasl-auth
-endif
+wocky_contact_factory_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_contact_factory_test_SOURCES = \
+ wocky-test-helper.c wocky-test-helper.h \
+ wocky-test-stream.c wocky-test-stream.h \
+ wocky-contact-factory-test.c
-wocky_sasl_utils_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_sasl_utils_test_SOURCES = wocky-sasl-utils-test.c
+wocky_data_form_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_data_form_test_SOURCES = \
+ wocky-test-helper.c wocky-test-helper.h \
+ wocky-test-stream.c wocky-test-stream.h \
+ wocky-data-form-test.c
-wocky_test_sasl_auth_DEPENDENCIES = $(LIBWOCKY)
-wocky_test_sasl_auth_SOURCES = \
- wocky-test-helper.c wocky-test-helper.h \
- wocky-test-sasl-auth.c \
- wocky-test-sasl-handler.c \
- wocky-test-sasl-handler.h \
- wocky-test-sasl-auth-server.c \
- wocky-test-sasl-auth-server.h \
- wocky-test-stream.c \
- wocky-test-stream.h
+wocky_dummy_xmpp_server_DEPENDENCIES = $(LIBWOCKY)
+wocky_dummy_xmpp_server_SOURCES = wocky-dummy-xmpp-server.c \
+ wocky-test-connector-server.c wocky-test-connector-server.h \
+ wocky-test-sasl-auth-server.c wocky-test-sasl-auth-server.h
+wocky_dummy_xmpp_server_LDADD = $(LDADD) @LIBSASL2_LIBS@
+wocky_dummy_xmpp_server_CFLAGS = $(AM_CFLAGS) @LIBSASL2_CFLAGS@ $(TLSDEFS)
-wocky_test_sasl_auth_LDADD = $(LDADD) @LIBSASL2_LIBS@
-wocky_test_sasl_auth_CFLAGS = $(AM_CFLAGS) @LIBSASL2_CFLAGS@
+wocky_http_proxy_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_http_proxy_test_SOURCES = wocky-http-proxy-test.c \
+ wocky-test-helper.c wocky-test-helper.h \
+ wocky-test-stream.c wocky-test-stream.h
-wocky_porter_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_porter_test_SOURCES = \
- wocky-porter-test.c \
+wocky_jid_validation_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_jid_validation_test_SOURCES = \
+ wocky-jid-validation-test.c \
wocky-test-helper.c wocky-test-helper.h \
wocky-test-stream.c wocky-test-stream.h
@@ -137,123 +146,122 @@ wocky_loopback_test_SOURCES = \
wocky-test-stream.c wocky-test-stream.h \
wocky-loopback-test.c
-wocky_xmpp_node_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_xmpp_node_test_SOURCES = \
- wocky-test-helper.c wocky-test-helper.h \
- wocky-test-stream.c wocky-test-stream.h \
- wocky-xmpp-node-test.c
-
wocky_node_tree_test_DEPENDENCIES = $(LIBWOCKY)
wocky_node_tree_test_SOURCES = \
wocky-test-helper.c wocky-test-helper.h \
wocky-test-stream.c wocky-test-stream.h \
wocky-node-tree-test.c
-wocky_stanza_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_stanza_test_SOURCES = \
+wocky_pep_service_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_pep_service_test_SOURCES = \
wocky-test-helper.c wocky-test-helper.h \
wocky-test-stream.c wocky-test-stream.h \
- wocky-stanza-test.c
+ wocky-pep-service-test.c
-wocky_roster_test_SOURCES = \
- wocky-roster-test.c \
+wocky_ping_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_ping_test_SOURCES = \
+ wocky-ping-test.c \
wocky-test-helper.c wocky-test-helper.h \
wocky-test-stream.c wocky-test-stream.h
-wocky_bare_contact_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_bare_contact_test_SOURCES = \
+wocky_porter_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_porter_test_SOURCES = \
+ wocky-porter-test.c \
wocky-test-helper.c wocky-test-helper.h \
- wocky-test-stream.c wocky-test-stream.h \
- wocky-bare-contact-test.c
+ wocky-test-stream.c wocky-test-stream.h
-wocky_resource_contact_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_resource_contact_test_SOURCES = \
+wocky_pubsub_node_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_pubsub_node_test_SOURCES = \
wocky-test-helper.c wocky-test-helper.h \
wocky-test-stream.c wocky-test-stream.h \
- wocky-resource-contact-test.c
+ wocky-pubsub-test-helpers.c wocky-pubsub-test-helpers.h \
+ wocky-pubsub-node-test.c
-wocky_contact_factory_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_contact_factory_test_SOURCES = \
+wocky_pubsub_service_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_pubsub_service_test_SOURCES = \
wocky-test-helper.c wocky-test-helper.h \
wocky-test-stream.c wocky-test-stream.h \
- wocky-contact-factory-test.c
+ wocky-pubsub-test-helpers.c wocky-pubsub-test-helpers.h \
+ wocky-pubsub-service-test.c
-wocky_session_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_session_test_SOURCES = \
+wocky_resource_contact_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_resource_contact_test_SOURCES = \
wocky-test-helper.c wocky-test-helper.h \
wocky-test-stream.c wocky-test-stream.h \
- wocky-session-test.c
+ wocky-resource-contact-test.c
-wocky_pep_service_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_pep_service_test_SOURCES = \
+wocky_roster_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_roster_test_SOURCES = \
+ wocky-roster-test.c \
wocky-test-helper.c wocky-test-helper.h \
- wocky-test-stream.c wocky-test-stream.h \
- wocky-pep-service-test.c
+ wocky-test-stream.c wocky-test-stream.h
-wocky_data_form_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_data_form_test_SOURCES = \
+wocky_sasl_utils_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_sasl_utils_test_SOURCES = wocky-sasl-utils-test.c
+
+wocky_scram_sha1_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_scram_sha1_test_SOURCES = wocky-scram-sha1-test.c \
wocky-test-helper.c wocky-test-helper.h \
- wocky-test-stream.c wocky-test-stream.h \
- wocky-data-form-test.c
+ wocky-test-stream.c wocky-test-stream.h
-wocky_pubsub_service_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_pubsub_service_test_SOURCES = \
+wocky_session_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_session_test_SOURCES = \
wocky-test-helper.c wocky-test-helper.h \
wocky-test-stream.c wocky-test-stream.h \
- wocky-pubsub-test-helpers.c wocky-pubsub-test-helpers.h \
- wocky-pubsub-service-test.c
+ wocky-session-test.c
-wocky_pubsub_node_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_pubsub_node_test_SOURCES = \
+wocky_stanza_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_stanza_test_SOURCES = \
wocky-test-helper.c wocky-test-helper.h \
wocky-test-stream.c wocky-test-stream.h \
- wocky-pubsub-test-helpers.c wocky-pubsub-test-helpers.h \
- wocky-pubsub-node-test.c
+ wocky-stanza-test.c
-wocky_ping_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_ping_test_SOURCES = \
- wocky-ping-test.c \
- wocky-test-helper.c wocky-test-helper.h \
- wocky-test-stream.c wocky-test-stream.h
+wocky_test_sasl_auth_DEPENDENCIES = $(LIBWOCKY)
+wocky_test_sasl_auth_SOURCES = \
+ wocky-test-helper.c wocky-test-helper.h \
+ wocky-test-sasl-auth.c \
+ wocky-test-sasl-handler.c \
+ wocky-test-sasl-handler.h \
+ wocky-test-sasl-auth-server.c \
+ wocky-test-sasl-auth-server.h \
+ wocky-test-stream.c \
+ wocky-test-stream.h
+wocky_test_sasl_auth_LDADD = $(LDADD) @LIBSASL2_LIBS@
+wocky_test_sasl_auth_CFLAGS = $(AM_CFLAGS) @LIBSASL2_CFLAGS@
-wocky_jid_validation_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_jid_validation_test_SOURCES = \
- wocky-jid-validation-test.c \
+wocky_tls_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_tls_test_SOURCES = \
+ wocky-tls-test.c \
wocky-test-helper.c wocky-test-helper.h \
wocky-test-stream.c wocky-test-stream.h
+wocky_tls_test_CFLAGS = $(AM_CFLAGS) $(TLSDEFS)
wocky_utils_test_DEPENDENCIES = $(LIBWOCKY)
wocky_utils_test_SOURCES = wocky-utils-test.c
-wocky_scram_sha1_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_scram_sha1_test_SOURCES = wocky-scram-sha1-test.c \
+wocky_xmpp_connection_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_xmpp_connection_test_SOURCES = \
+ wocky-xmpp-connection-test.c \
wocky-test-helper.c wocky-test-helper.h \
wocky-test-stream.c wocky-test-stream.h
-wocky_http_proxy_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_http_proxy_test_SOURCES = wocky-http-proxy-test.c \
+wocky_xmpp_node_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_xmpp_node_test_SOURCES = \
wocky-test-helper.c wocky-test-helper.h \
- wocky-test-stream.c wocky-test-stream.h
-
-if HAVE_LIBSASL2
- wocky_dummy_xmpp_server_DEPENDENCIES = $(LIBWOCKY)
- wocky_dummy_xmpp_server_SOURCES = wocky-dummy-xmpp-server.c \
- wocky-test-connector-server.c wocky-test-connector-server.h \
- wocky-test-sasl-auth-server.c wocky-test-sasl-auth-server.h
- wocky_dummy_xmpp_server_LDADD = $(LDADD) @LIBSASL2_LIBS@
- wocky_dummy_xmpp_server_CFLAGS = $(AM_CFLAGS) @LIBSASL2_CFLAGS@ $(TLSDEFS)
-
- noinst_PROGRAMS += wocky-dummy-xmpp-server
-endif
+ wocky-test-stream.c wocky-test-stream.h \
+ wocky-xmpp-node-test.c
-wocky_caps_hash_test_DEPENDENCIES = $(LIBWOCKY)
-wocky_caps_hash_test_SOURCES = wocky-caps-hash-test.c \
+wocky_xmpp_reader_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_xmpp_reader_test_SOURCES = \
wocky-test-helper.c wocky-test-helper.h \
- wocky-test-stream.c wocky-test-stream.h
+ wocky-test-stream.c wocky-test-stream.h \
+ wocky-xmpp-reader-test.c
-if HAVE_GIO_PROXY
- TEST_PROGS += wocky-http-proxy-test
-endif
+wocky_xmpp_readwrite_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_xmpp_readwrite_test_SOURCES = \
+ wocky-test-stream.c wocky-test-stream.h \
+ wocky-test-helper.c wocky-test-helper.h \
+ wocky-xmpp-readwrite-test.c
AM_CFLAGS = $(ERROR_CFLAGS) $(GCOV_CFLAGS) @GLIB_CFLAGS@ \
@LIBXML2_CFLAGS@ @TLS_CFLAGS@ @WOCKY_CFLAGS@
diff --git a/tests/certs/badwild-cert.cfg b/tests/certs/badwild-cert.cfg
new file mode 100644
index 0000000..10637bc
--- /dev/null
+++ b/tests/certs/badwild-cert.cfg
@@ -0,0 +1,89 @@
+# X.509 Certificate options
+#
+# DN options
+
+# The organization of the subject.
+organization = "Collabora"
+
+# The organizational unit of the subject.
+unit = "Wocky Test Suite"
+
+# The locality of the subject.
+# locality =
+
+# The state of the certificate owner.
+state = "Dazed"
+
+# The country of the subject. Two letter code.
+country = UK
+
+# The common name of the certificate owner.
+cn = "Wocky XMPP Library"
+
+# A user id of the certificate owner.
+#uid = "clauper"
+
+# If the supported DN OIDs are not adequate you can set
+# any OID here.
+# For example set the X.520 Title and the X.520 Pseudonym
+# by using OID and string pairs.
+#dn_oid = "2.5.4.12" "Dr." "2.5.4.65" "jackal"
+
+# This is deprecated and should not be used in new
+# certificates.
+# pkcs9_email = "none@none.org"
+
+# The serial number of the certificate
+serial = 002
+
+# In how many days, counting from today, this certificate will expire.
+expiration_days = 10220
+
+# X.509 v3 extensions
+
+# A dnsname in case of a WWW server.
+dns_name = "*easel-juice.org"
+#dns_name = "www.morethanone.org"
+
+# An IP address in case of a server.
+ip_address = "127.0.0.1"
+
+# An email in case of a person
+#email = "postmaster@collabora.co.uk"
+
+# An URL that has CRLs (certificate revocation lists)
+# available. Needed in CA certificates.
+#crl_dist_points = "file:///tmp/wocky-tests/crl"
+
+# Whether this is a CA certificate or not
+#ca
+
+# Whether this certificate will be used for a TLS client
+tls_www_client
+
+# Whether this certificate will be used for a TLS server
+tls_www_server
+
+# Whether this certificate will be used to sign data (needed
+# in TLS DHE ciphersuites).
+#signing_key
+
+# Whether this certificate will be used to encrypt data (needed
+# in TLS RSA ciphersuites). Note that it is prefered to use different
+# keys for encryption and signing.
+encryption_key
+
+# Whether this key will be used to sign other certificates.
+#cert_signing_key
+
+# Whether this key will be used to sign CRLs.
+#crl_signing_key
+
+# Whether this key will be used to sign code.
+#code_signing_key
+
+# Whether this key will be used to sign OCSP data.
+#ocsp_signing_key
+
+# Whether this key will be used for time stamping.
+time_stamping_key
diff --git a/tests/certs/badwild-cert.pem b/tests/certs/badwild-cert.pem
new file mode 100644
index 0000000..5d845da
--- /dev/null
+++ b/tests/certs/badwild-cert.pem
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIEUzCCAzugAwIBAgIBAjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJVSzES
+MBAGA1UEChMJQ29sbGFib3JhMRkwFwYDVQQLExBXb2NreSBUZXN0IFN1aXRlMREw
+DwYDVQQIEwhDb25mdXNlZDEbMBkGA1UEAxMSV29ja3kgWE1QUCBMaWJyYXJ5MB4X
+DTEyMDUwODE3MjMxNFoXDTQwMDUwMTE3MjMxNFowaTELMAkGA1UEBhMCVUsxEjAQ
+BgNVBAoTCUNvbGxhYm9yYTEZMBcGA1UECxMQV29ja3kgVGVzdCBTdWl0ZTEOMAwG
+A1UECBMFRGF6ZWQxGzAZBgNVBAMTEldvY2t5IFhNUFAgTGlicmFyeTCCAVIwDQYJ
+KoZIhvcNAQEBBQADggE/ADCCAToCggExAKqugc6RDo23Xf4k7c6ZcWAcennYpZ56
+DN4onsqvL7fu5oqorMSWuZnHB6YV74q+Fu0UDLXS9TE4+Vvn3eJrEh4VkcKLqoFs
+gflGhQS4eul4G1JERPQmux3JkyRNrsJECTncyqW5A82YpFh9SaHWaifxb6Y24G9u
+/kxf3G8QdqKjXNI3JH8bTKzAJWfs+lSYOarwzkGPnnOZCKU5rKXiKOyP+0tG1KnH
+G9FH/4eX1JfmNT4VyoAwFjVTBw1228z2bnyt/MSFCVnbnYcNU381rYFjq6O9bzf9
+Cfglb58AbwJnKIoaCn2AzavpMXYmEjt9vplZ3DQFBcC5/ysUWW22C2HjqSz0BcQ0
+a3+qpql2gdr+tKAAactY7yC1HI095eMLRxMLN5aedImfAW8o/aVOBsUCAwEAAaOB
+0jCBzzAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD
+ATAhBgNVHREEGjAYghAqZWFzZWwtanVpY2Uub3JnhwR/AAABMA8GA1UdDwEB/wQF
+AwMHIAAwHQYDVR0OBBYEFAOsVIF450rphWkweqxR8bnRaBnmMB8GA1UdIwQYMBaA
+FEkwJgiMmtZpzYvAzA5cAo5KH6aMMCwGA1UdHwQlMCMwIaAfoB2GG2ZpbGU6Ly8v
+dG1wL3dvY2t5LXRlc3RzL2NybDANBgkqhkiG9w0BAQsFAAOCAQEAeJIQ1SZS3UQZ
+1aBh3901WZhYozH38i7I7JclEQO0Kla2et289IAc2IiocEoeMjwE3bO+1xO4EF2N
+zEgGzodaIsVGol9zlrRm9CXeYgRBPkgorebFWfEigIsLY3VjrhBhV3UnDTY7Hebr
+tqdOX7Xa0DLud1AqGzNia2aon28qY05qu5RPgxfvpRUYnJJlT5tidSkbXJIuvbb0
+roFd+es4qgQU+ZiAc6DHpREcShxd+20JJVR9zvqeM7egP3tZPiFf8u6n3CsYKQ0p
+be3NygY35lwc1VYxX/PMecslbsHQoa+ZWgd2+lcZ/81QD2JrmRo7nG3clsCbXodj
+1c8cuflfhw==
+-----END CERTIFICATE-----
diff --git a/tests/certs/badwild-key.pem b/tests/certs/badwild-key.pem
new file mode 100644
index 0000000..c151ef8
--- /dev/null
+++ b/tests/certs/badwild-key.pem
@@ -0,0 +1,145 @@
+Public Key Info:
+ Public Key Algorithm: RSA
+ Key Security Level: Normal
+
+modulus:
+ 00:aa:ae:81:ce:91:0e:8d:b7:5d:fe:24:ed:ce:99:
+ 71:60:1c:7a:79:d8:a5:9e:7a:0c:de:28:9e:ca:af:
+ 2f:b7:ee:e6:8a:a8:ac:c4:96:b9:99:c7:07:a6:15:
+ ef:8a:be:16:ed:14:0c:b5:d2:f5:31:38:f9:5b:e7:
+ dd:e2:6b:12:1e:15:91:c2:8b:aa:81:6c:81:f9:46:
+ 85:04:b8:7a:e9:78:1b:52:44:44:f4:26:bb:1d:c9:
+ 93:24:4d:ae:c2:44:09:39:dc:ca:a5:b9:03:cd:98:
+ a4:58:7d:49:a1:d6:6a:27:f1:6f:a6:36:e0:6f:6e:
+ fe:4c:5f:dc:6f:10:76:a2:a3:5c:d2:37:24:7f:1b:
+ 4c:ac:c0:25:67:ec:fa:54:98:39:aa:f0:ce:41:8f:
+ 9e:73:99:08:a5:39:ac:a5:e2:28:ec:8f:fb:4b:46:
+ d4:a9:c7:1b:d1:47:ff:87:97:d4:97:e6:35:3e:15:
+ ca:80:30:16:35:53:07:0d:76:db:cc:f6:6e:7c:ad:
+ fc:c4:85:09:59:db:9d:87:0d:53:7f:35:ad:81:63:
+ ab:a3:bd:6f:37:fd:09:f8:25:6f:9f:00:6f:02:67:
+ 28:8a:1a:0a:7d:80:cd:ab:e9:31:76:26:12:3b:7d:
+ be:99:59:dc:34:05:05:c0:b9:ff:2b:14:59:6d:b6:
+ 0b:61:e3:a9:2c:f4:05:c4:34:6b:7f:aa:a6:a9:76:
+ 81:da:fe:b4:a0:00:69:cb:58:ef:20:b5:1c:8d:3d:
+ e5:e3:0b:47:13:0b:37:96:9e:74:89:9f:01:6f:28:
+ fd:a5:4e:06:c5:
+public exponent:
+ 01:00:01:
+private exponent:
+ 30:f2:e9:01:3f:b2:87:cb:fd:a5:c4:1b:7b:33:a0:
+ 43:2b:07:a8:e8:0b:df:5e:2b:50:8f:1f:b3:0a:f8:
+ c9:6c:37:2b:ae:e2:15:63:10:89:5c:7e:02:10:aa:
+ 69:04:c8:f8:66:d6:9a:52:8c:c4:f8:0c:f3:61:94:
+ cd:b9:5d:31:c9:87:ca:de:59:20:d2:e8:97:7f:b4:
+ e8:3c:ab:eb:46:e9:b6:f7:23:3b:4d:dc:3d:d7:b0:
+ 5e:29:f5:23:7b:75:95:e5:8f:2c:65:da:04:5d:71:
+ 4c:7b:69:e1:82:e7:60:db:29:e4:e6:3a:09:5b:30:
+ 8c:e0:b5:0c:19:11:9c:e4:f2:da:ca:bb:a9:c4:3f:
+ 82:0f:7c:ea:c8:18:a6:a8:1d:69:bb:b3:bb:33:a0:
+ 02:90:79:e1:78:fc:56:ca:a6:fa:fc:42:59:2f:43:
+ c2:d6:66:88:d7:9f:83:01:60:25:bb:31:f3:67:90:
+ 51:6f:8f:9b:54:e6:54:a3:6a:b9:e7:15:21:8f:03:
+ bb:31:b4:63:77:c8:b6:88:b6:f2:ee:88:c7:c8:e6:
+ 7a:3e:05:25:5f:fe:6a:54:11:0f:44:57:18:4c:34:
+ a4:fe:01:07:65:68:bb:6c:b2:f4:df:a3:74:94:65:
+ ba:84:37:26:6a:75:4f:38:81:46:43:51:43:21:3e:
+ 1e:1b:8c:cd:28:41:d9:23:8b:c3:7b:b1:31:21:8e:
+ 51:cc:7d:a7:4b:bd:ec:f7:34:45:df:e3:b1:4b:5e:
+ 86:4d:03:3e:ea:b5:21:4c:7b:e2:08:a2:6e:94:df:
+ 6c:c8:0d:65:
+prime1:
+ 00:c9:35:da:a7:66:35:95:86:65:2d:87:e9:c4:34:
+ c8:af:6d:8b:d7:2a:ab:cd:f5:db:52:27:f6:3b:07:
+ 15:2c:f1:ce:e9:01:87:69:c7:50:7d:48:fc:86:ac:
+ ac:c6:27:f3:e4:94:fd:03:dd:1a:10:dd:c4:9c:28:
+ d6:eb:52:42:3a:32:d2:83:14:73:1a:75:a8:a5:ac:
+ 86:76:1b:4f:84:67:4f:a7:9a:4b:d2:11:fc:0d:8f:
+ 45:46:e1:cc:ce:a7:01:b8:eb:c5:0b:f0:90:56:0d:
+ b1:04:a6:3f:06:a0:d8:03:be:df:7c:51:6f:14:f1:
+ dc:3e:08:0d:a5:6c:cc:6b:68:ec:c6:d3:d2:35:cf:
+ 28:c5:d7:36:f6:7e:71:6c:7e:1e:12:a3:e3:64:8b:
+ 1e:94:83:
+prime2:
+ 00:d9:28:88:32:c2:21:fd:24:1e:10:5c:ae:c9:f4:
+ 85:8f:f6:a1:a9:23:39:f9:8a:c6:17:f0:a1:db:fa:
+ f0:51:a0:66:e7:db:3b:39:c7:2f:c1:62:7d:e3:bc:
+ 8a:27:5d:99:0c:11:69:a0:f0:8e:62:22:56:a9:c5:
+ d5:9e:ce:6f:c0:47:9c:c8:90:86:ad:b0:2e:56:64:
+ 35:2c:b6:1d:f1:71:bf:0e:e5:bf:24:1d:40:02:34:
+ ba:37:00:d3:f5:19:65:3c:78:89:20:f8:72:b7:50:
+ 0f:c9:e7:bd:5c:10:fe:b5:d0:07:66:7b:3d:e6:e0:
+ f3:81:b9:7a:19:48:ce:fe:5e:e7:d8:65:f3:d7:9b:
+ f9:95:98:f7:bc:44:0d:a2:24:50:3b:8e:99:f3:5b:
+ 7d:65:17:
+coefficient:
+ 24:63:2a:2a:91:68:87:30:89:7f:df:a4:22:1f:b2:
+ 22:db:e9:4f:c3:00:87:da:7c:62:d3:ff:19:cb:6d:
+ 01:a9:71:9e:03:4f:66:a2:92:ea:5b:e4:d6:6e:d9:
+ 71:a9:25:49:41:a9:aa:e3:1b:ef:7f:1b:57:83:c4:
+ 78:cd:39:47:d0:af:79:3a:a5:e1:fb:48:4a:f4:d9:
+ 02:db:96:f0:3a:d1:3b:7b:b6:f8:28:1e:2c:1d:a2:
+ 4c:f9:19:29:52:6c:6f:70:19:49:37:59:9d:fd:6c:
+ 36:3e:c8:e0:50:6b:49:ff:2b:d9:31:72:c7:38:67:
+ cc:30:49:40:06:ba:70:63:36:bd:11:19:1a:f2:d1:
+ 2b:f3:09:cc:ed:b6:23:11:4c:9d:9d:d5:56:25:06:
+ bc:9b:
+exp1:
+ 2d:cb:78:0c:89:92:c0:89:6a:15:c6:cd:49:be:c9:
+ be:43:ac:84:38:4e:09:fe:5f:00:7f:df:e6:e7:61:
+ c6:6c:f2:ae:cd:8e:48:60:f8:cc:cb:03:ce:93:16:
+ 6f:b3:40:2b:52:4d:93:c7:8a:db:33:de:3f:bc:7b:
+ cd:eb:56:ef:70:09:c1:93:b1:ee:df:c0:96:94:81:
+ b5:f7:d9:4d:45:46:37:db:42:4e:79:91:68:74:a7:
+ 4b:ce:b0:92:88:28:38:32:51:12:ba:99:df:3c:16:
+ 81:fc:64:73:d9:bf:d0:7a:6c:db:1a:9d:b1:a2:aa:
+ 81:e3:cb:57:4f:3f:e7:62:66:21:8a:ac:59:5a:e0:
+ e8:c6:66:d8:0b:47:3d:6e:26:e2:0f:32:fd:fb:3b:
+ e6:89:
+exp2:
+ 77:5c:ef:18:2f:5b:2d:60:87:e1:e8:7f:ee:e4:27:
+ f2:0c:d7:a9:37:82:ab:66:9b:22:17:93:70:6e:0b:
+ 60:62:b1:8d:aa:14:70:da:ca:a6:1a:74:26:14:c1:
+ 3f:88:14:12:ed:13:49:72:50:61:22:8c:ce:3e:be:
+ ff:ce:6a:e0:9e:bc:50:06:18:f1:29:91:1e:cd:6f:
+ e9:06:a5:88:cc:43:ff:75:4b:4e:17:81:d2:74:97:
+ 12:9a:b2:e6:db:31:a9:3e:7b:e9:92:86:c3:ba:0f:
+ 23:a4:ec:91:c9:89:a7:f8:13:c9:41:de:b1:a4:5e:
+ 54:a4:d5:b7:46:2c:f8:e6:c3:bb:4f:b6:eb:81:81:
+ 7b:b8:1c:1b:f0:b0:29:ba:9a:1b:52:73:ce:af:30:
+ 4a:bb:
+
+Public Key ID: 03:AC:54:81:78:E7:4A:E9:85:69:30:7A:AC:51:F1:B9:D1:68:19:E6
+
+-----BEGIN RSA PRIVATE KEY-----
+MIIFegIBAAKCATEAqq6BzpEOjbdd/iTtzplxYBx6edilnnoM3iieyq8vt+7miqis
+xJa5mccHphXvir4W7RQMtdL1MTj5W+fd4msSHhWRwouqgWyB+UaFBLh66XgbUkRE
+9Ca7HcmTJE2uwkQJOdzKpbkDzZikWH1JodZqJ/Fvpjbgb27+TF/cbxB2oqNc0jck
+fxtMrMAlZ+z6VJg5qvDOQY+ec5kIpTmspeIo7I/7S0bUqccb0Uf/h5fUl+Y1PhXK
+gDAWNVMHDXbbzPZufK38xIUJWdudhw1TfzWtgWOro71vN/0J+CVvnwBvAmcoihoK
+fYDNq+kxdiYSO32+mVncNAUFwLn/KxRZbbYLYeOpLPQFxDRrf6qmqXaB2v60oABp
+y1jvILUcjT3l4wtHEws3lp50iZ8Bbyj9pU4GxQIDAQABAoIBMDDy6QE/sofL/aXE
+G3szoEMrB6joC99eK1CPH7MK+MlsNyuu4hVjEIlcfgIQqmkEyPhm1ppSjMT4DPNh
+lM25XTHJh8reWSDS6Jd/tOg8q+tG6bb3IztN3D3XsF4p9SN7dZXljyxl2gRdcUx7
+aeGC52DbKeTmOglbMIzgtQwZEZzk8trKu6nEP4IPfOrIGKaoHWm7s7szoAKQeeF4
+/FbKpvr8QlkvQ8LWZojXn4MBYCW7MfNnkFFvj5tU5lSjarnnFSGPA7sxtGN3yLaI
+tvLuiMfI5no+BSVf/mpUEQ9EVxhMNKT+AQdlaLtssvTfo3SUZbqENyZqdU84gUZD
+UUMhPh4bjM0oQdkji8N7sTEhjlHMfadLvez3NEXf47FLXoZNAz7qtSFMe+IIom6U
+32zIDWUCgZkAyTXap2Y1lYZlLYfpxDTIr22L1yqrzfXbUif2OwcVLPHO6QGHacdQ
+fUj8hqysxifz5JT9A90aEN3EnCjW61JCOjLSgxRzGnWopayGdhtPhGdPp5pL0hH8
+DY9FRuHMzqcBuOvFC/CQVg2xBKY/BqDYA77ffFFvFPHcPggNpWzMa2jsxtPSNc8o
+xdc29n5xbH4eEqPjZIselIMCgZkA2SiIMsIh/SQeEFyuyfSFj/ahqSM5+YrGF/Ch
+2/rwUaBm59s7OccvwWJ947yKJ12ZDBFpoPCOYiJWqcXVns5vwEecyJCGrbAuVmQ1
+LLYd8XG/DuW/JB1AAjS6NwDT9RllPHiJIPhyt1APyee9XBD+tdAHZns95uDzgbl6
+GUjO/l7n2GXz15v5lZj3vEQNoiRQO46Z81t9ZRcCgZgty3gMiZLAiWoVxs1Jvsm+
+Q6yEOE4J/l8Af9/m52HGbPKuzY5IYPjMywPOkxZvs0ArUk2Tx4rbM94/vHvN61bv
+cAnBk7Hu38CWlIG199lNRUY320JOeZFodKdLzrCSiCg4MlESupnfPBaB/GRz2b/Q
+emzbGp2xoqqB48tXTz/nYmYhiqxZWuDoxmbYC0c9bibiDzL9+zvmiQKBmHdc7xgv
+Wy1gh+Hof+7kJ/IM16k3gqtmmyIXk3BuC2BisY2qFHDayqYadCYUwT+IFBLtE0ly
+UGEijM4+vv/OauCevFAGGPEpkR7Nb+kGpYjMQ/91S04XgdJ0lxKasubbMak+e+mS
+hsO6DyOk7JHJiaf4E8lB3rGkXlSk1bdGLPjmw7tPtuuBgXu4HBvwsCm6mhtSc86v
+MEq7AoGYJGMqKpFohzCJf9+kIh+yItvpT8MAh9p8YtP/GcttAalxngNPZqKS6lvk
+1m7ZcaklSUGpquMb738bV4PEeM05R9CveTql4ftISvTZAtuW8DrRO3u2+CgeLB2i
+TPkZKVJsb3AZSTdZnf1sNj7I4FBrSf8r2TFyxzhnzDBJQAa6cGM2vREZGvLRK/MJ
+zO22IxFMnZ3VViUGvJs=
+-----END RSA PRIVATE KEY-----
diff --git a/tests/certs/ca-0-crl.cfg b/tests/certs/ca-0-crl.cfg
new file mode 100644
index 0000000..bc9ed25
--- /dev/null
+++ b/tests/certs/ca-0-crl.cfg
@@ -0,0 +1,89 @@
+# X.509 Certificate options
+#
+# DN options
+
+# The organization of the subject.
+organization = "Collabora"
+
+# The organizational unit of the subject.
+unit = "Wocky Test Suite"
+
+# The locality of the subject.
+# locality =
+
+# The state of the certificate owner.
+state = "Confused"
+
+# The country of the subject. Two letter code.
+country = UK
+
+# The common name of the certificate owner.
+cn = "Wocky XMPP Library"
+
+# A user id of the certificate owner.
+#uid = "clauper"
+
+# If the supported DN OIDs are not adequate you can set
+# any OID here.
+# For example set the X.520 Title and the X.520 Pseudonym
+# by using OID and string pairs.
+#dn_oid = "2.5.4.12" "Dr." "2.5.4.65" "jackal"
+
+# This is deprecated and should not be used in new
+# certificates.
+# pkcs9_email = "none@none.org"
+
+# The serial number of the certificate
+serial = 001
+
+# In how many days, counting from today, this certificate will expire.
+expiration_days = 10220
+
+# X.509 v3 extensions
+
+# A dnsname in case of a WWW server.
+#dns_name = "www.none.org"
+#dns_name = "www.morethanone.org"
+
+# An IP address in case of a server.
+#ip_address = "192.168.1.1"
+
+# An email in case of a person
+email = "postmaster@collabora.co.uk"
+
+# An URL that has CRLs (certificate revocation lists)
+# available. Needed in CA certificates.
+crl_dist_points = "file:///tmp/wocky-tests/crl"
+
+# Whether this is a CA certificate or not
+#ca
+
+# Whether this certificate will be used for a TLS client
+#tls_www_client
+
+# Whether this certificate will be used for a TLS server
+#tls_www_server
+
+# Whether this certificate will be used to sign data (needed
+# in TLS DHE ciphersuites).
+signing_key
+
+# Whether this certificate will be used to encrypt data (needed
+# in TLS RSA ciphersuites). Note that it is prefered to use different
+# keys for encryption and signing.
+#encryption_key
+
+# Whether this key will be used to sign other certificates.
+cert_signing_key
+
+# Whether this key will be used to sign CRLs.
+crl_signing_key
+
+# Whether this key will be used to sign code.
+#code_signing_key
+
+# Whether this key will be used to sign OCSP data.
+#ocsp_signing_key
+
+# Whether this key will be used for time stamping.
+#time_stamping_key
diff --git a/tests/certs/ca-0-crl.pem b/tests/certs/ca-0-crl.pem
new file mode 100644
index 0000000..80f47ba
--- /dev/null
+++ b/tests/certs/ca-0-crl.pem
@@ -0,0 +1,13 @@
+-----BEGIN X509 CRL-----
+MIIB/DCB5QIBATANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJVSzESMBAGA1UE
+ChMJQ29sbGFib3JhMRkwFwYDVQQLExBXb2NreSBUZXN0IFN1aXRlMREwDwYDVQQI
+EwhDb25mdXNlZDEbMBkGA1UEAxMSV29ja3kgWE1QUCBMaWJyYXJ5Fw0xMjA1MTAx
+NjQzNTBaFw0xMzA1MTAxNjQzNTBaMBQwEgIBCxcNMTIwNTEwMTY0MzUwWqAvMC0w
+HwYDVR0jBBgwFoAUSTAmCIya1mnNi8DMDlwCjkofpowwCgYDVR0UBAMCAQAwDQYJ
+KoZIhvcNAQELBQADggEBACFaj/M6g+fP0RQEiB7kvoocdM7XGUemkl9Ns/chc9zH
+yLgq1891jIO5GoKoCuMGEFfYat/VZutNOLFHkJ0AeqrvOSPVZ8atcZTJR/lgjR6I
+PN/UMFpHMEVa7cUtLPx47UvGDolrOo1d4ciLVUUPoZMRGxTitVz8KtEk+O9s6NjS
+W25uTGoNT58OQS51dXq4N97gNMSeggWGN1Y9swv0s992G/Y93t/uQvsRsSEMe7kj
+ddChE3Gb4I+7TkjL+e64RlYsAtvMePM3k3+Zk95wFGWqlwRow46Nv3F02C8Af5JV
+zp+tsq2foM0lIADnOTjUs2XgNGqx0Gm/hTAfBMsIgkM=
+-----END X509 CRL-----
diff --git a/tests/certs/cas/8a76ade9.0 b/tests/certs/cas/8a76ade9.0
new file mode 120000
index 0000000..9e72607
--- /dev/null
+++ b/tests/certs/cas/8a76ade9.0
@@ -0,0 +1 @@
+ca-1-cert.pem \ No newline at end of file
diff --git a/tests/certs/cas/ae5bb84e.0 b/tests/certs/cas/ae5bb84e.0
new file mode 120000
index 0000000..7df3360
--- /dev/null
+++ b/tests/certs/cas/ae5bb84e.0
@@ -0,0 +1 @@
+ca-2-cert.pem \ No newline at end of file
diff --git a/tests/certs/cas/e7df1717.0 b/tests/certs/cas/e7df1717.0
new file mode 120000
index 0000000..f938b72
--- /dev/null
+++ b/tests/certs/cas/e7df1717.0
@@ -0,0 +1 @@
+ca-0-cert.pem \ No newline at end of file
diff --git a/tests/certs/crl/ca-0-crl.pem b/tests/certs/crl/ca-0-crl.pem
new file mode 100644
index 0000000..80f47ba
--- /dev/null
+++ b/tests/certs/crl/ca-0-crl.pem
@@ -0,0 +1,13 @@
+-----BEGIN X509 CRL-----
+MIIB/DCB5QIBATANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJVSzESMBAGA1UE
+ChMJQ29sbGFib3JhMRkwFwYDVQQLExBXb2NreSBUZXN0IFN1aXRlMREwDwYDVQQI
+EwhDb25mdXNlZDEbMBkGA1UEAxMSV29ja3kgWE1QUCBMaWJyYXJ5Fw0xMjA1MTAx
+NjQzNTBaFw0xMzA1MTAxNjQzNTBaMBQwEgIBCxcNMTIwNTEwMTY0MzUwWqAvMC0w
+HwYDVR0jBBgwFoAUSTAmCIya1mnNi8DMDlwCjkofpowwCgYDVR0UBAMCAQAwDQYJ
+KoZIhvcNAQELBQADggEBACFaj/M6g+fP0RQEiB7kvoocdM7XGUemkl9Ns/chc9zH
+yLgq1891jIO5GoKoCuMGEFfYat/VZutNOLFHkJ0AeqrvOSPVZ8atcZTJR/lgjR6I
+PN/UMFpHMEVa7cUtLPx47UvGDolrOo1d4ciLVUUPoZMRGxTitVz8KtEk+O9s6NjS
+W25uTGoNT58OQS51dXq4N97gNMSeggWGN1Y9swv0s992G/Y93t/uQvsRsSEMe7kj
+ddChE3Gb4I+7TkjL+e64RlYsAtvMePM3k3+Zk95wFGWqlwRow46Nv3F02C8Af5JV
+zp+tsq2foM0lIADnOTjUs2XgNGqx0Gm/hTAfBMsIgkM=
+-----END X509 CRL-----
diff --git a/tests/certs/crl/e7df1717.r0 b/tests/certs/crl/e7df1717.r0
new file mode 120000
index 0000000..f0c8057
--- /dev/null
+++ b/tests/certs/crl/e7df1717.r0
@@ -0,0 +1 @@
+ca-0-crl.pem \ No newline at end of file
diff --git a/tests/certs/ss-cert.cfg b/tests/certs/ss-cert.cfg
index c311d9c..1171cee 100644
--- a/tests/certs/ss-cert.cfg
+++ b/tests/certs/ss-cert.cfg
@@ -56,7 +56,7 @@ email = "postmaster@collabora.co.uk"
crl_dist_points = "file:///tmp/wocky-tests/crl"
# Whether this is a CA certificate or not
-#ca
+ca
# Whether this certificate will be used for a TLS client
tls_www_client
diff --git a/tests/certs/ss-cert.pem b/tests/certs/ss-cert.pem
index b69a488..67f59f9 100644
--- a/tests/certs/ss-cert.pem
+++ b/tests/certs/ss-cert.pem
@@ -1,24 +1,24 @@
-----BEGIN CERTIFICATE-----
-MIID/jCCAuigAwIBAgIBATALBgkqhkiG9w0BAQUwbDELMAkGA1UEBhMCVUsxEjAQ
+MIIECDCCAvCgAwIBAgIBATANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJVSzES
+MBAGA1UEChMJQ29sbGFib3JhMRkwFwYDVQQLExBXb2NreSBUZXN0IFN1aXRlMREw
+DwYDVQQIEwhDb25mdXNlZDEbMBkGA1UEAxMSV29ja3kgWE1QUCBMaWJyYXJ5MB4X
+DTEyMDUwOTIxMTkwNFoXDTQwMDUwMjIxMTkwNFowbDELMAkGA1UEBhMCVUsxEjAQ
BgNVBAoTCUNvbGxhYm9yYTEZMBcGA1UECxMQV29ja3kgVGVzdCBTdWl0ZTERMA8G
-A1UECBMIQ29uZnVzZWQxGzAZBgNVBAMTEldvY2t5IFhNUFAgTGlicmFyeTAeFw0w
-OTA5MTgxMjU0MTFaFw0zNzA5MTExMjU0MTFaMGwxCzAJBgNVBAYTAlVLMRIwEAYD
-VQQKEwlDb2xsYWJvcmExGTAXBgNVBAsTEFdvY2t5IFRlc3QgU3VpdGUxETAPBgNV
-BAgTCENvbmZ1c2VkMRswGQYDVQQDExJXb2NreSBYTVBQIExpYnJhcnkwggEfMAsG
-CSqGSIb3DQEBAQOCAQ4AMIIBCQKCAQC4QOrqKaMDcaVqLepcTueaIemPXx9C2E/4
-lKHDLIhxMV8aHv4NYTlEfredqwBBhjB9jFIp8HVM10UtM6XC+bqqRnF+kK7NBCm7
-Xn35vMe7C+mezVJDtldVsIvOLQ59iQjKi9evyggUZ+xohiXJ3RhI/j/l1qXluhl+
-wAj2Y0y/wu0/zkHBRRmaRWilCEYa1oVdnR7D6Rum8+JMnLgfIqOQZP13zeXSydke
-ptrumEbFm0fX0ZxNcaTPfA6rWlWIhk0ATrvoHoX7Rh2WnO1wtve4QgHgsQ5CG6q7
-NPwUot3dOHCNXkgIo2PQ/q04eY3DSakmpmxZlwoDZe68x6SryYB5AgMBAAGjgbEw
-ga4wDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEw
-IQYDVR0RBBowGIIQd2Vhc2VsLWp1aWNlLm9yZ4cEfwAAATAPBgNVHQ8BAf8EBQMD
-B6AAMB0GA1UdDgQWBBRYzBRAh+uls4FHgbD3WjU8Q4nrLjAsBgNVHR8EJTAjMCGg
-H6AdhhtmaWxlOi8vL3RtcC93b2NreS10ZXN0cy9jcmwwCwYJKoZIhvcNAQEFA4IB
-AQBtnC/spdJmvbIBe1dE2ahtxVoFvNO42nqK9pd21GPKJtja89eimtmGAKZgXUvb
-r7mvv7edntmWSJH6p5J/BPFI+ZJmm0kcmFXMAy6Uo5aueM61Z2zvkZRuPIRSYrC+
-5219RpUD8GMGP+VQ75LuhPQq03cpJIfU50092n6nowXDr9CBxYphre4/m6/fFfQa
-Eqj+eTpqXXdcU7SxhyTYyTov9IDRNSk/d0vr5Kb3rXU31AtdfCxKRejeSWa57XC1
-EW0dDSdEmHoAo5YQplPDqekSnYGl6oqidKTk0WJI0Ud1ZMLOBotGrK9ccclS3IBq
-uUXfCgLlQ/aazJ2OPgcci4aM
+A1UECBMIQ29uZnVzZWQxGzAZBgNVBAMTEldvY2t5IFhNUFAgTGlicmFyeTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALhA6uopowNxpWot6lxO55oh6Y9f
+H0LYT/iUocMsiHExXxoe/g1hOUR+t52rAEGGMH2MUinwdUzXRS0zpcL5uqpGcX6Q
+rs0EKbteffm8x7sL6Z7NUkO2V1Wwi84tDn2JCMqL16/KCBRn7GiGJcndGEj+P+XW
+peW6GX7ACPZjTL/C7T/OQcFFGZpFaKUIRhrWhV2dHsPpG6bz4kycuB8io5Bk/XfN
+5dLJ2R6m2u6YRsWbR9fRnE1xpM98DqtaVYiGTQBOu+gehftGHZac7XC297hCAeCx
+DkIbqrs0/BSi3d04cI1eSAijY9D+rTh5jcNJqSambFmXCgNl7rzHpKvJgHkCAwEA
+AaOBtDCBsTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr
+BgEFBQcDATAhBgNVHREEGjAYghB3ZWFzZWwtanVpY2Uub3JnhwR/AAABMA8GA1Ud
+DwEB/wQFAwMHpgAwHQYDVR0OBBYEFJSBitgIFe+DDoAc7lAGk5+gbReHMCwGA1Ud
+HwQlMCMwIaAfoB2GG2ZpbGU6Ly8vdG1wL3dvY2t5LXRlc3RzL2NybDANBgkqhkiG
+9w0BAQsFAAOCAQEArMZFCHoZHZGpT7XrsWHJALVIixkwtWePte9iiznKbl4ywrlZ
+fRAOoPUl+ZrspDU0lA5299V67M2WMIMs9uAjW5jgCPswylkO1RaJBTHcHmiGm2Ev
+R/8h4PCDo/LJvQjQs3B7Wev5zQEE25h8DUxvqA499wRWPxVmiLt4n/yFuVMNUDAF
+eQvCDv2YQu8EZ/A++kQjbjujTHT3okSXoPEcOZj+C1Eqp8cWXRrrh8xkE9ptRWIS
+Q+MhjvgVPrniGBBK1Mh0DbQhlriLD++XYI3bDwlg/Hnc0o9bWv0sfyvtOI45g5QG
+SDeBQ8L0KFUfu03ZBwATpd9pHUngpWbVXGqLhg==
-----END CERTIFICATE-----
diff --git a/tests/certs/wild-cert.cfg b/tests/certs/wild-cert.cfg
new file mode 100644
index 0000000..d5aa039
--- /dev/null
+++ b/tests/certs/wild-cert.cfg
@@ -0,0 +1,89 @@
+# X.509 Certificate options
+#
+# DN options
+
+# The organization of the subject.
+organization = "Collabora"
+
+# The organizational unit of the subject.
+unit = "Wocky Test Suite"
+
+# The locality of the subject.
+# locality =
+
+# The state of the certificate owner.
+state = "Dazed"
+
+# The country of the subject. Two letter code.
+country = UK
+
+# The common name of the certificate owner.
+cn = "Wocky XMPP Library"
+
+# A user id of the certificate owner.
+#uid = "clauper"
+
+# If the supported DN OIDs are not adequate you can set
+# any OID here.
+# For example set the X.520 Title and the X.520 Pseudonym
+# by using OID and string pairs.
+#dn_oid = "2.5.4.12" "Dr." "2.5.4.65" "jackal"
+
+# This is deprecated and should not be used in new
+# certificates.
+# pkcs9_email = "none@none.org"
+
+# The serial number of the certificate
+serial = 002
+
+# In how many days, counting from today, this certificate will expire.
+expiration_days = 10220
+
+# X.509 v3 extensions
+
+# A dnsname in case of a WWW server.
+dns_name = "*.weasel-juice.org"
+#dns_name = "www.morethanone.org"
+
+# An IP address in case of a server.
+ip_address = "127.0.0.1"
+
+# An email in case of a person
+#email = "postmaster@collabora.co.uk"
+
+# An URL that has CRLs (certificate revocation lists)
+# available. Needed in CA certificates.
+#crl_dist_points = "file:///tmp/wocky-tests/crl"
+
+# Whether this is a CA certificate or not
+#ca
+
+# Whether this certificate will be used for a TLS client
+tls_www_client
+
+# Whether this certificate will be used for a TLS server
+tls_www_server
+
+# Whether this certificate will be used to sign data (needed
+# in TLS DHE ciphersuites).
+#signing_key
+
+# Whether this certificate will be used to encrypt data (needed
+# in TLS RSA ciphersuites). Note that it is prefered to use different
+# keys for encryption and signing.
+encryption_key
+
+# Whether this key will be used to sign other certificates.
+#cert_signing_key
+
+# Whether this key will be used to sign CRLs.
+#crl_signing_key
+
+# Whether this key will be used to sign code.
+#code_signing_key
+
+# Whether this key will be used to sign OCSP data.
+#ocsp_signing_key
+
+# Whether this key will be used for time stamping.
+time_stamping_key
diff --git a/tests/certs/wild-cert.pem b/tests/certs/wild-cert.pem
new file mode 100644
index 0000000..966b894
--- /dev/null
+++ b/tests/certs/wild-cert.pem
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIEVTCCAz2gAwIBAgIBAjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJVSzES
+MBAGA1UEChMJQ29sbGFib3JhMRkwFwYDVQQLExBXb2NreSBUZXN0IFN1aXRlMREw
+DwYDVQQIEwhDb25mdXNlZDEbMBkGA1UEAxMSV29ja3kgWE1QUCBMaWJyYXJ5MB4X
+DTEyMDUwODE3MjMxNFoXDTQwMDUwMTE3MjMxNFowaTELMAkGA1UEBhMCVUsxEjAQ
+BgNVBAoTCUNvbGxhYm9yYTEZMBcGA1UECxMQV29ja3kgVGVzdCBTdWl0ZTEOMAwG
+A1UECBMFRGF6ZWQxGzAZBgNVBAMTEldvY2t5IFhNUFAgTGlicmFyeTCCAVIwDQYJ
+KoZIhvcNAQEBBQADggE/ADCCAToCggExAOLThsSZX/OomslmhfbLQ4QL+rLXfAVN
+/17JZR4WRYWeX6JZUHNSMYJ2B98JZtU/aRHdNV46or6y+0UD1AFC543wp9KetcLb
+YaX2Lsxc70soBtfb6L02TQbaMf2q84/YWxwO5xCEG10KGLRrgAbxqtMvYIdGXstH
+9TDCr6La6Iqeoq7R/4dBgF30hVTsOnai7W+YQuPT18QlX1O7HdaffXDGZPC/+0PY
+zPdr2vHlSzifNOnttPZuo4ALBE3hhf4xnxh531wdVQhsS6WTX5RhFU9ox4WsG7Q3
+y5tQJQ72ARPWqVZiK1WJIDuEVlpWOutEd1McI3GVMuZt80PZaJC3uJs7f6l5Xux9
+yUrE5ztzg378w5Hi6Rt3C0rLjHzn78MiC6DGlcG5KMOB+O4wgFzHCScCAwEAAaOB
+1DCB0TAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD
+ATAjBgNVHREEHDAaghIqLndlYXNlbC1qdWljZS5vcmeHBH8AAAEwDwYDVR0PAQH/
+BAUDAwcgADAdBgNVHQ4EFgQUdpNYWe7MfIU0lyUQvttyQMxXTewwHwYDVR0jBBgw
+FoAUSTAmCIya1mnNi8DMDlwCjkofpowwLAYDVR0fBCUwIzAhoB+gHYYbZmlsZTov
+Ly90bXAvd29ja3ktdGVzdHMvY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQBd0wScKbZR
++VhPij0/MWoQCwo7SpXyOCc1oTH5Cj2FKg70Al2fyfNXeQ94QybK2IFUuhuj6QuC
+2kr11a8yUaYj5GfbMP0JHjJn8EKeRlxsv7q37nGkFcN4hweR3UfrY4dhEh195ZlM
+f3odQaKfyE4ST/SmVzGrzxwVupcuHcxCD4VowYMjO2xl2XeMjTMmkRhuW/kTcVHb
+xdhkLekyD5AEI3XndFKDRufFXg5nGmV7t5A/i1gKxGi/Co2YeTYd1XDspK0GSyX7
+hl6RoZrGPkYF3mUmv6E/SiL8inCMfvt56aSQttX9zqB7zXOTXFnFOho+EYb6ZxvL
+liKZJQZ39OX7
+-----END CERTIFICATE-----
diff --git a/tests/certs/wild-key.pem b/tests/certs/wild-key.pem
new file mode 100644
index 0000000..0913fa4
--- /dev/null
+++ b/tests/certs/wild-key.pem
@@ -0,0 +1,145 @@
+Public Key Info:
+ Public Key Algorithm: RSA
+ Key Security Level: Normal
+
+modulus:
+ 00:e2:d3:86:c4:99:5f:f3:a8:9a:c9:66:85:f6:cb:
+ 43:84:0b:fa:b2:d7:7c:05:4d:ff:5e:c9:65:1e:16:
+ 45:85:9e:5f:a2:59:50:73:52:31:82:76:07:df:09:
+ 66:d5:3f:69:11:dd:35:5e:3a:a2:be:b2:fb:45:03:
+ d4:01:42:e7:8d:f0:a7:d2:9e:b5:c2:db:61:a5:f6:
+ 2e:cc:5c:ef:4b:28:06:d7:db:e8:bd:36:4d:06:da:
+ 31:fd:aa:f3:8f:d8:5b:1c:0e:e7:10:84:1b:5d:0a:
+ 18:b4:6b:80:06:f1:aa:d3:2f:60:87:46:5e:cb:47:
+ f5:30:c2:af:a2:da:e8:8a:9e:a2:ae:d1:ff:87:41:
+ 80:5d:f4:85:54:ec:3a:76:a2:ed:6f:98:42:e3:d3:
+ d7:c4:25:5f:53:bb:1d:d6:9f:7d:70:c6:64:f0:bf:
+ fb:43:d8:cc:f7:6b:da:f1:e5:4b:38:9f:34:e9:ed:
+ b4:f6:6e:a3:80:0b:04:4d:e1:85:fe:31:9f:18:79:
+ df:5c:1d:55:08:6c:4b:a5:93:5f:94:61:15:4f:68:
+ c7:85:ac:1b:b4:37:cb:9b:50:25:0e:f6:01:13:d6:
+ a9:56:62:2b:55:89:20:3b:84:56:5a:56:3a:eb:44:
+ 77:53:1c:23:71:95:32:e6:6d:f3:43:d9:68:90:b7:
+ b8:9b:3b:7f:a9:79:5e:ec:7d:c9:4a:c4:e7:3b:73:
+ 83:7e:fc:c3:91:e2:e9:1b:77:0b:4a:cb:8c:7c:e7:
+ ef:c3:22:0b:a0:c6:95:c1:b9:28:c3:81:f8:ee:30:
+ 80:5c:c7:09:27:
+public exponent:
+ 01:00:01:
+private exponent:
+ 55:09:ac:8a:e4:5d:7e:c2:05:55:e0:63:f5:04:7b:
+ 89:73:dc:47:54:56:20:be:ff:30:90:1c:a2:cd:02:
+ bf:77:82:af:21:00:f2:5b:2c:48:96:eb:98:88:b3:
+ ce:da:f9:0f:43:79:90:9c:37:0b:7b:9a:8c:63:45:
+ 06:3c:09:07:c3:e3:87:29:0d:47:c2:6b:b7:86:b6:
+ 40:d3:ce:ba:c4:84:dc:44:8b:da:f4:12:a8:b1:00:
+ f0:f3:38:61:03:62:15:00:d9:4b:ed:db:3c:64:5e:
+ a2:b5:72:c8:27:ae:3d:82:93:93:e3:a4:02:5e:35:
+ e4:53:f8:f8:fb:4d:17:3b:26:0e:98:98:4e:23:16:
+ 77:23:07:21:b1:76:b1:a7:9f:90:a8:ea:da:f3:3e:
+ ff:3f:b6:be:19:7e:15:b0:3c:7d:75:f6:7d:58:bc:
+ cd:da:ba:4a:ac:b5:ab:91:a9:71:b7:bf:db:0c:ca:
+ ff:7a:72:0e:98:64:78:af:26:fc:07:df:8a:2e:9f:
+ 51:3f:b3:d7:14:9f:1e:5a:0d:73:58:3c:37:67:5e:
+ 4e:6c:f4:bd:e4:58:ae:aa:bd:eb:68:e3:ed:9c:78:
+ af:de:80:9b:6a:93:01:ce:8e:b9:1b:ea:c4:cc:a8:
+ df:55:8c:fb:27:6b:c1:64:9b:ed:48:c8:80:86:e3:
+ 2d:a9:67:0a:7e:be:83:c4:aa:33:24:a1:c4:b4:2d:
+ 17:72:6e:45:9f:81:b3:61:34:03:38:a2:6e:48:84:
+ c3:aa:4b:9b:ba:0f:af:c2:2d:e0:b8:55:bb:01:3b:
+ 6e:bb:77:81:
+prime1:
+ 00:e6:06:0c:d6:72:1a:a9:3e:91:2f:5c:ca:a4:36:
+ 2a:0e:1c:b8:e8:86:e8:c8:8d:f3:01:da:58:1c:5a:
+ fb:71:23:84:66:0f:ec:00:f7:0a:ce:f4:0b:bb:93:
+ 29:fe:50:ce:39:03:ed:0f:67:3e:a2:2d:3e:fa:c3:
+ 1b:bd:cd:59:22:b8:57:35:f3:14:e9:4a:a6:62:22:
+ 99:9b:a3:1a:b4:2b:d3:be:d0:7b:0b:8a:fb:5c:3c:
+ 6e:05:45:58:c4:13:e0:96:33:b7:da:b0:14:dc:fa:
+ 07:57:68:33:ab:a8:ab:1f:26:f6:96:ea:f2:5c:02:
+ ee:99:28:ec:2a:65:f6:6f:6f:4e:5e:65:87:95:b3:
+ 65:70:05:f2:15:37:6f:f7:94:67:19:98:b9:94:f1:
+ 68:d5:47:
+prime2:
+ 00:fc:71:0a:87:55:5d:b1:46:2a:95:0b:c4:38:ea:
+ 38:71:bc:ce:68:8c:4b:0a:34:cc:50:df:50:d9:84:
+ 1d:e3:9a:29:45:90:36:54:a0:f8:ce:69:82:06:5c:
+ c9:db:e3:2f:90:86:ec:2f:bc:b6:ed:95:72:1c:b8:
+ 1c:58:de:f5:ee:24:99:3c:e5:ab:8b:89:c0:94:c2:
+ 63:a2:c9:2c:b1:7d:ad:f5:f5:fa:e7:04:84:e5:08:
+ a6:c7:8a:46:92:5d:45:0f:b0:e9:b5:9f:a0:6c:43:
+ 72:14:eb:00:e1:9a:af:9d:5e:2f:be:a3:d6:82:b2:
+ c2:db:c9:3d:5a:42:31:a8:8b:26:6e:ee:50:96:48:
+ 57:fc:96:2a:6c:ce:dd:d6:1c:59:22:06:0f:63:c8:
+ 51:9d:21:
+coefficient:
+ 00:8b:25:6f:8d:58:ca:17:d7:66:73:7d:c1:22:b3:
+ fa:f6:09:b7:2c:b6:95:47:66:d5:17:97:51:cd:21:
+ df:aa:d6:29:41:6d:c2:22:7e:dc:28:8a:a8:ab:fa:
+ ba:c8:84:d3:45:be:9b:1f:8d:b6:c2:c6:17:88:65:
+ 39:19:f4:1f:fc:98:a3:27:ae:4b:c1:3b:b3:b4:d8:
+ 76:89:ec:51:c2:56:bc:53:e7:6c:1f:39:ab:37:97:
+ 03:32:67:c8:15:0c:67:9f:6f:7c:92:3f:b3:12:b1:
+ ec:ef:98:11:65:fd:96:20:5a:27:5d:fc:3b:cb:f0:
+ 7a:c8:be:dd:02:c0:52:36:da:7e:9a:99:32:3f:44:
+ 8e:fe:84:29:5b:44:8c:ea:42:22:18:4b:a9:00:e7:
+ 63:e1:3a:
+exp1:
+ 00:ae:50:cd:6f:c1:de:e4:7d:2e:c7:46:b9:a6:82:
+ 09:92:d2:3f:6a:af:ba:3f:2a:1b:8b:3e:ed:60:e7:
+ ad:ff:0a:5c:6e:80:08:68:9e:ce:89:11:36:c6:fa:
+ 7a:6b:68:cf:2f:34:03:75:95:d7:48:4d:a3:99:a3:
+ 3c:25:b5:35:a4:73:30:5c:09:0f:c2:cf:b8:91:4c:
+ 09:3e:81:f7:5d:ac:8c:f5:e0:c6:2e:74:2f:92:b1:
+ 94:ea:d7:a7:b2:48:21:fd:91:c9:f5:a7:54:d8:35:
+ 7e:54:c5:f9:ca:4f:b3:06:93:9f:71:b5:30:df:7b:
+ b5:57:4a:44:d6:c8:98:5d:d0:6b:02:6c:4c:b8:ac:
+ cf:e5:7a:0b:ff:fa:18:16:f6:56:bf:76:16:c5:81:
+ c0:e1:47:
+exp2:
+ 39:1a:b5:20:02:0e:8c:b0:6b:a7:85:9e:e0:a5:13:
+ 19:9f:75:2d:af:36:b6:5b:55:30:a2:23:9e:e0:c5:
+ 06:1c:74:63:99:08:c1:42:1c:a6:4c:b5:ae:0c:c5:
+ 58:e9:5e:74:1a:21:49:77:2e:06:36:7d:36:c3:eb:
+ 1d:ab:6a:04:71:e0:fc:26:94:14:9f:97:a1:cf:0b:
+ 4c:e2:a7:2a:8b:5c:93:bc:0e:02:be:41:f5:b1:6c:
+ 50:cd:15:c3:bc:37:88:22:23:ec:02:ec:fe:30:8f:
+ 98:ed:c4:28:44:9e:9f:41:94:19:52:e4:8e:72:33:
+ a4:16:34:bb:bb:27:92:91:cf:a2:de:63:fa:6a:e9:
+ 04:69:4f:b5:87:62:25:32:0a:73:a8:dd:48:5c:23:
+ 22:21:
+
+Public Key ID: 76:93:58:59:EE:CC:7C:85:34:97:25:10:BE:DB:72:40:CC:57:4D:EC
+
+-----BEGIN RSA PRIVATE KEY-----
+MIIFfAIBAAKCATEA4tOGxJlf86iayWaF9stDhAv6std8BU3/XsllHhZFhZ5follQ
+c1IxgnYH3wlm1T9pEd01XjqivrL7RQPUAULnjfCn0p61wtthpfYuzFzvSygG19vo
+vTZNBtox/arzj9hbHA7nEIQbXQoYtGuABvGq0y9gh0Zey0f1MMKvotroip6irtH/
+h0GAXfSFVOw6dqLtb5hC49PXxCVfU7sd1p99cMZk8L/7Q9jM92va8eVLOJ806e20
+9m6jgAsETeGF/jGfGHnfXB1VCGxLpZNflGEVT2jHhawbtDfLm1AlDvYBE9apVmIr
+VYkgO4RWWlY660R3UxwjcZUy5m3zQ9lokLe4mzt/qXle7H3JSsTnO3ODfvzDkeLp
+G3cLSsuMfOfvwyILoMaVwbkow4H47jCAXMcJJwIDAQABAoIBMFUJrIrkXX7CBVXg
+Y/UEe4lz3EdUViC+/zCQHKLNAr93gq8hAPJbLEiW65iIs87a+Q9DeZCcNwt7moxj
+RQY8CQfD44cpDUfCa7eGtkDTzrrEhNxEi9r0EqixAPDzOGEDYhUA2Uvt2zxkXqK1
+csgnrj2Ck5PjpAJeNeRT+Pj7TRc7Jg6YmE4jFncjByGxdrGnn5Co6trzPv8/tr4Z
+fhWwPH119n1YvM3aukqstauRqXG3v9sMyv96cg6YZHivJvwH34oun1E/s9cUnx5a
+DXNYPDdnXk5s9L3kWK6qveto4+2ceK/egJtqkwHOjrkb6sTMqN9VjPsna8Fkm+1I
+yICG4y2pZwp+voPEqjMkocS0LRdybkWfgbNhNAM4om5IhMOqS5u6D6/CLeC4VbsB
+O267d4ECgZkA5gYM1nIaqT6RL1zKpDYqDhy46IboyI3zAdpYHFr7cSOEZg/sAPcK
+zvQLu5Mp/lDOOQPtD2c+oi0++sMbvc1ZIrhXNfMU6UqmYiKZm6MatCvTvtB7C4r7
+XDxuBUVYxBPgljO32rAU3PoHV2gzq6irHyb2luryXALumSjsKmX2b29OXmWHlbNl
+cAXyFTdv95RnGZi5lPFo1UcCgZkA/HEKh1VdsUYqlQvEOOo4cbzOaIxLCjTMUN9Q
+2YQd45opRZA2VKD4zmmCBlzJ2+MvkIbsL7y27ZVyHLgcWN717iSZPOWri4nAlMJj
+oskssX2t9fX65wSE5Qimx4pGkl1FD7DptZ+gbENyFOsA4ZqvnV4vvqPWgrLC28k9
+WkIxqIsmbu5QlkhX/JYqbM7d1hxZIgYPY8hRnSECgZkArlDNb8He5H0ux0a5poIJ
+ktI/aq+6Pyobiz7tYOet/wpcboAIaJ7OiRE2xvp6a2jPLzQDdZXXSE2jmaM8JbU1
+pHMwXAkPws+4kUwJPoH3XayM9eDGLnQvkrGU6tenskgh/ZHJ9adU2DV+VMX5yk+z
+BpOfcbUw33u1V0pE1siYXdBrAmxMuKzP5XoL//oYFvZWv3YWxYHA4UcCgZg5GrUg
+Ag6MsGunhZ7gpRMZn3Utrza2W1UwoiOe4MUGHHRjmQjBQhymTLWuDMVY6V50GiFJ
+dy4GNn02w+sdq2oEceD8JpQUn5ehzwtM4qcqi1yTvA4CvkH1sWxQzRXDvDeIIiPs
+Auz+MI+Y7cQoRJ6fQZQZUuSOcjOkFjS7uyeSkc+i3mP6aukEaU+1h2IlMgpzqN1I
+XCMiIQKBmQCLJW+NWMoX12ZzfcEis/r2CbcstpVHZtUXl1HNId+q1ilBbcIiftwo
+iqir+rrIhNNFvpsfjbbCxheIZTkZ9B/8mKMnrkvBO7O02HaJ7FHCVrxT52wfOas3
+lwMyZ8gVDGefb3ySP7MSsezvmBFl/ZYgWidd/DvL8HrIvt0CwFI22n6amTI/RI7+
+hClbRIzqQiIYS6kA52PhOg==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/wocky-connector-test.c b/tests/wocky-connector-test.c
index 8e9288c..c6914f0 100644
--- a/tests/wocky-connector-test.c
+++ b/tests/wocky-connector-test.c
@@ -19,6 +19,10 @@
#include <arpa/inet.h>
#endif
+#ifdef G_OS_UNIX
+#include <netinet/tcp.h>
+#endif
+
#include <wocky/wocky.h>
#include "wocky-test-connector-server.h"
@@ -188,7 +192,7 @@ test_t tests[] =
{ { TLS_SUPPORT, AUTH_MECH_OR_NULL_FOR_ALL },
{ SERVER_PROBLEM..., CONNECTOR_PROBLEM... },
{ USERNAME, PASSWORD },
- SERVER_LISTEN_PORT },
+ SERVER_LISTEN_PORT, SERVER_CERT },
// Fake DNS Record:
// SRV_HOSTs SRV record → { HOSTNAME, PORT }
@@ -2774,6 +2778,78 @@ test_t tests[] =
{ "moose@tomato-juice.org", "something", PLAIN, TLS },
{ NULL, 0, XMPP_V1 } } },
+ { "/connector/cert-verification/tls/wildcard/ok",
+ QUIET,
+ { S_NO_ERROR },
+ { { TLS, NULL },
+ { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK },
+ { "moose", "something" },
+ PORT_XMPP, CERT_WILDCARD },
+ { "foo.weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE },
+ { TLS_REQUIRED,
+ { "moose@foo.weasel-juice.org", "something", PLAIN, TLS },
+ { NULL, 0, XMPP_V1, STARTTLS, CERT_CHECK_STRICT, TLS_CA_DIR } } },
+
+ { "/connector/cert-verification/tls/wildcard/level-mismatch/fail",
+ QUIET,
+ { S_WOCKY_TLS_CERT_ERROR, WOCKY_TLS_CERT_NAME_MISMATCH, -1 },
+ { { TLS, NULL },
+ { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK },
+ { "moose", "something" },
+ PORT_XMPP, CERT_WILDCARD },
+ { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE },
+ { PLAINTEXT_OK,
+ { "moose@weasel-juice.org", "something", PLAIN, TLS },
+ { NULL, 0, XMPP_V1, STARTTLS, CERT_CHECK_STRICT, TLS_CA_DIR } } },
+
+ { "/connector/cert-verification/tls/wildcard/glob-mismatch/fail",
+ QUIET,
+ { S_WOCKY_TLS_CERT_ERROR, WOCKY_TLS_CERT_NAME_MISMATCH, -1 },
+ { { TLS, NULL },
+ { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK },
+ { "moose", "something" },
+ PORT_XMPP, CERT_WILDCARD },
+ { "foo.diesel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE },
+ { TLS_REQUIRED,
+ { "moose@foo.diesel-juice.org", "something", PLAIN, TLS },
+ { NULL, 0, XMPP_V1, STARTTLS, CERT_CHECK_STRICT, TLS_CA_DIR } } },
+
+ { "/connector/cert-verification/tls/bad-wildcard/fail",
+ QUIET,
+ { S_WOCKY_TLS_CERT_ERROR, WOCKY_TLS_CERT_NAME_MISMATCH, -1 },
+ { { TLS, NULL },
+ { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK },
+ { "moose", "something" },
+ PORT_XMPP, CERT_BADWILD },
+ { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE },
+ { TLS_REQUIRED,
+ { "moose@weasel-juice.org", "something", PLAIN, TLS },
+ { NULL, 0, XMPP_V1, STARTTLS, CERT_CHECK_STRICT, TLS_CA_DIR } } },
+
+ { "/connector/cert-verification/tls/revoked/fail",
+ QUIET,
+ { S_WOCKY_TLS_CERT_ERROR, WOCKY_TLS_CERT_REVOKED, -1 },
+ { { TLS, NULL },
+ { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK },
+ { "moose", "something" },
+ PORT_XMPP, CERT_REVOKED },
+ { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE },
+ { TLS_REQUIRED,
+ { "moose@weasel-juice.org", "something", PLAIN, TLS },
+ { NULL, 0, XMPP_V1, STARTTLS, CERT_CHECK_STRICT, TLS_CA_DIR } } },
+
+ { "/connector/cert-verification/tls/revoked/lenient/fail",
+ QUIET,
+ { S_WOCKY_TLS_CERT_ERROR, WOCKY_TLS_CERT_REVOKED, -1 },
+ { { TLS, NULL },
+ { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK },
+ { "moose", "something" },
+ PORT_XMPP, CERT_REVOKED },
+ { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE },
+ { TLS_REQUIRED,
+ { "moose@weasel-juice.org", "something", PLAIN, TLS },
+ { NULL, 0, XMPP_V1, STARTTLS, CERT_CHECK_LENIENT, TLS_CA_DIR } } },
+
/* ********************************************************************* */
/* as above but with legacy ssl */
{ "/connector/cert-verification/ssl/nohost/ok",
@@ -2872,6 +2948,78 @@ test_t tests[] =
{ "moose@weasel-juice.org", "something", PLAIN, TLS },
{ NULL, 0, XMPP_V1, OLD_SSL } } },
+ { "/connector/cert-verification/ssl/wildcard/ok",
+ QUIET,
+ { S_NO_ERROR },
+ { { TLS, NULL },
+ { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } },
+ { "moose", "something" },
+ PORT_XMPP, CERT_WILDCARD },
+ { "foo.weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE },
+ { TLS_REQUIRED,
+ { "moose@foo.weasel-juice.org", "something", PLAIN, TLS },
+ { NULL, 0, XMPP_V1, OLD_SSL, CERT_CHECK_STRICT, TLS_CA_DIR } } },
+
+ { "/connector/cert-verification/ssl/wildcard/level-mismatch/fail",
+ QUIET,
+ { S_WOCKY_TLS_CERT_ERROR, WOCKY_TLS_CERT_NAME_MISMATCH, -1 },
+ { { TLS, NULL },
+ { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } },
+ { "moose", "something" },
+ PORT_XMPP, CERT_WILDCARD },
+ { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE },
+ { PLAINTEXT_OK,
+ { "moose@weasel-juice.org", "something", PLAIN, TLS },
+ { NULL, 0, XMPP_V1, OLD_SSL, CERT_CHECK_STRICT, TLS_CA_DIR } } },
+
+ { "/connector/cert-verification/ssl/wildcard/glob-mismatch/fail",
+ QUIET,
+ { S_WOCKY_TLS_CERT_ERROR, WOCKY_TLS_CERT_NAME_MISMATCH, -1 },
+ { { TLS, NULL },
+ { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } },
+ { "moose", "something" },
+ PORT_XMPP, CERT_WILDCARD },
+ { "foo.diesel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE },
+ { TLS_REQUIRED,
+ { "moose@foo.diesel-juice.org", "something", PLAIN, TLS },
+ { NULL, 0, XMPP_V1, OLD_SSL, CERT_CHECK_STRICT, TLS_CA_DIR } } },
+
+ { "/connector/cert-verification/ssl/bad-wildcard/fail",
+ QUIET,
+ { S_WOCKY_TLS_CERT_ERROR, WOCKY_TLS_CERT_NAME_MISMATCH, -1 },
+ { { TLS, NULL },
+ { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } },
+ { "moose", "something" },
+ PORT_XMPP, CERT_BADWILD },
+ { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE },
+ { TLS_REQUIRED,
+ { "moose@weasel-juice.org", "something", PLAIN, TLS },
+ { NULL, 0, XMPP_V1, OLD_SSL, CERT_CHECK_STRICT, TLS_CA_DIR } } },
+
+ { "/connector/cert-verification/ssl/revoked/fail",
+ QUIET,
+ { S_WOCKY_TLS_CERT_ERROR, WOCKY_TLS_CERT_REVOKED, -1 },
+ { { TLS, NULL },
+ { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } },
+ { "moose", "something" },
+ PORT_XMPP, CERT_REVOKED },
+ { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE },
+ { TLS_REQUIRED,
+ { "moose@weasel-juice.org", "something", PLAIN, TLS },
+ { NULL, 0, XMPP_V1, OLD_SSL, CERT_CHECK_STRICT, TLS_CA_DIR } } },
+
+ { "/connector/cert-verification/ssl/revoked/lenient/fail",
+ QUIET,
+ { S_WOCKY_TLS_CERT_ERROR, WOCKY_TLS_CERT_REVOKED, -1 },
+ { { TLS, NULL },
+ { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } },
+ { "moose", "something" },
+ PORT_XMPP, CERT_REVOKED },
+ { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE },
+ { TLS_REQUIRED,
+ { "moose@weasel-juice.org", "something", PLAIN, TLS },
+ { NULL, 0, XMPP_V1, OLD_SSL, CERT_CHECK_LENIENT, TLS_CA_DIR } } },
+
/* ********************************************************************* */
/* certificate non-verification tests */
{ "/connector/cert-nonverification/tls/nohost/ok",
@@ -3190,6 +3338,9 @@ start_dummy_xmpp_server (ServerParameters *srv)
server.sin_port = htons (port);
ssock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
setsockopt (ssock, SOL_SOCKET, SO_REUSEADDR, (const char *) &reuse, sizeof (reuse));
+#ifdef G_OS_UNIX
+ setsockopt (ssock, IPPROTO_TCP, TCP_NODELAY, (const char *) &reuse, sizeof (reuse));
+#endif
res = bind (ssock, (struct sockaddr *) &server, sizeof (server));
@@ -3307,6 +3458,21 @@ test_done (GObject *source,
typedef void (*test_func) (gconstpointer);
+#ifdef G_OS_UNIX
+static void
+connection_established_cb (WockyConnector *connector,
+ GSocketConnection *conn,
+ gpointer user_data)
+{
+ GSocket *sock = g_socket_connection_get_socket (conn);
+ gint fd, flag = 1;
+
+ fd = g_socket_get_fd (sock);
+ setsockopt (fd, IPPROTO_TCP, TCP_NODELAY,
+ (const char *) &flag, sizeof (flag));
+}
+#endif
+
static gboolean
start_test (gpointer data)
{
@@ -3375,16 +3541,33 @@ run_test (gpointer data)
"tls-handler" , handler,
NULL);
+ /* Make sure we only use the test CAs, not system-wide ones. */
+ wocky_tls_handler_forget_cas (handler);
+ g_assert (wocky_tls_handler_get_cas (handler) == NULL);
+
/* check if the cert paths are valid */
g_assert (g_file_test (TLS_CA_CRT_FILE, G_FILE_TEST_EXISTS));
wocky_tls_handler_add_ca (handler, ca);
+ /* not having a CRL can expose a bug in the openssl error handling
+ * (basically we get 'CRL not fetched' instead of 'Expired'):
+ * The bug has been fixed, but we can keep checking for it by
+ * dropping the CRLs when the test is for an expired cert */
+ if (test->server_parameters.cert != CERT_EXPIRED)
+ wocky_tls_handler_add_crl (handler, TLS_CRL_DIR);
+
g_object_unref (handler);
test->connector = wcon;
g_idle_add (start_test, test);
+#ifdef G_OS_UNIX
+ /* set TCP_NODELAY as soon as possible */
+ g_signal_connect (test->connector, "connection-established",
+ G_CALLBACK (connection_established_cb), NULL);
+#endif
+
g_main_loop_run (mainloop);
if (test->result.domain == S_NO_ERROR)
@@ -3427,7 +3610,7 @@ run_test (gpointer data)
if (!strcmp (test->desc, CONNECTOR_INTERNALS_TEST))
{
int i;
- gchar *identity = NULL;
+ gchar *identity, *session_id, *resource;
WockyConnector *tmp =
wocky_connector_new ("foo@bar.org", "abc", "xyz", NULL, NULL);
WockyStanza *feat = NULL;
@@ -3453,15 +3636,17 @@ run_test (gpointer data)
g_object_unref (feat);
identity = NULL;
- g_object_get (wcon, "session-id", &identity, NULL);
- g_assert (identity != NULL);
- g_assert (*identity != '\0');
- g_free (identity);
+ g_object_get (wcon, "session-id", &session_id, NULL);
+ g_assert (session_id != NULL);
+ g_assert (*session_id != '\0');
+ g_free (session_id);
- g_object_get (wcon, "resource", &identity, NULL);
- g_assert (identity != NULL);
- g_assert (*identity != '\0');
- g_free (identity);
+ g_object_get (wcon, "resource", &resource, NULL);
+ /* TODO: really? :resource gets updated to contain the actual
+ * post-bind resource, but perhaps :resource should be updated too?
+ */
+ g_assert_cmpstr (resource, ==, NULL);
+ g_free (resource);
g_object_get (wcon, "legacy", &jabber, "old-ssl", &oldssl, NULL);
g_assert (jabber == (gboolean)(xproblem & XMPP_PROBLEM_OLD_SERVER));
diff --git a/tests/wocky-node-tree-test.c b/tests/wocky-node-tree-test.c
index bb2183f..70a56b7 100644
--- a/tests/wocky-node-tree-test.c
+++ b/tests/wocky-node-tree-test.c
@@ -66,6 +66,7 @@ test_node_add_tree (void)
{
WockyNodeTree *origin, *ashes, *destination;
WockyNode *ash, *top;
+ WockyNode *ash_copy;
origin = wocky_node_tree_new ("Eyjafjallajökull", "urn:wocky:lol:ísland",
'(', "æsc",
@@ -80,7 +81,9 @@ test_node_add_tree (void)
destination = wocky_node_tree_new ("europe", "urn:wocky:lol:noplanesforyou",
'*', &top, NULL);
- wocky_node_add_node_tree (top, ashes);
+ ash_copy = wocky_node_add_node_tree (top, ashes);
+
+ test_assert_nodes_equal (ash_copy, ash);
test_assert_nodes_equal (
wocky_node_get_first_child (wocky_node_tree_get_top_node (destination)),
diff --git a/tests/wocky-porter-test.c b/tests/wocky-porter-test.c
index 9eb420d..abcd5c3 100644
--- a/tests/wocky-porter-test.c
+++ b/tests/wocky-porter-test.c
@@ -3529,7 +3529,6 @@ main (int argc, char **argv)
int result;
test_init (argc, argv);
- g_test_bug_base ("http://bugs.freedesktop.org/show_bug.cgi?id=");
g_test_add_func ("/xmpp-porter/initiation", test_instantiation);
g_test_add_func ("/xmpp-porter/send", test_send);
diff --git a/tests/wocky-stanza-test.c b/tests/wocky-stanza-test.c
index 3936e77..77290df 100644
--- a/tests/wocky-stanza-test.c
+++ b/tests/wocky-stanza-test.c
@@ -586,6 +586,52 @@ test_extract_errors_legacy_code (void)
g_object_unref (stanza);
}
+
+static void
+test_extract_errors_unrecognised_condition (void)
+{
+ WockyNodeTree *error_tree;
+ WockyXmppErrorType type;
+ GError *core = NULL, *specialized = NULL;
+ WockyNode *specialized_node = NULL;
+ const gchar *text = "The room is currently overactive, please try again later";
+
+ /* I got a <policy-violation> error back from prosody with type='wait', but
+ * Wocky didn't know about policy-violation (which was introduced in
+ * RFC6120). Not only did it ignore <policy-violation>, it also ignored
+ * type='wait' and returned the default, WOCKY_XMPP_ERROR_TYPE_CANCEL.
+ */
+ g_test_bug ("43166#c9");
+
+ error_tree = wocky_node_tree_new ("error", WOCKY_XMPP_NS_JABBER_CLIENT,
+ '@', "type", "wait",
+ '(', "typo-violation", ':', WOCKY_XMPP_NS_STANZAS, ')',
+ '(', "text", ':', WOCKY_XMPP_NS_STANZAS,
+ '$', text,
+ ')', NULL);
+
+ wocky_xmpp_error_extract (wocky_node_tree_get_top_node (error_tree),
+ &type, &core, &specialized, &specialized_node);
+
+ g_assert_cmpuint (type, ==, WOCKY_XMPP_ERROR_TYPE_WAIT);
+
+ /* Wocky should default to undefined-condition when the server returns an
+ * unknown core error element.
+ */
+ g_assert_error (core, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_UNDEFINED_CONDITION);
+ g_assert_cmpstr (core->message, ==, text);
+ g_clear_error (&core);
+
+ /* The unrecognised error element was in the :xmpp-stanzas namespace, so it
+ * shouldn't be returned as a specialized error.
+ */
+ g_assert_no_error (specialized);
+ g_assert (specialized_node == NULL);
+
+ g_object_unref (error_tree);
+}
+
+
static void
test_extract_errors_no_sense (void)
{
@@ -757,6 +803,21 @@ test_stanza_error_to_node_jingle (void)
g_clear_error (&specialized);
}
+static void
+test_unknown (
+ gconstpointer name_null_ns)
+{
+ const gchar *name = name_null_ns;
+ const gchar *ns = name + strlen (name) + 1;
+ WockyStanza *stanza = wocky_stanza_new (name, ns);
+ WockyStanzaType type;
+
+ wocky_stanza_get_type_info (stanza, &type, NULL);
+ g_assert_cmpuint (type, ==, WOCKY_STANZA_TYPE_UNKNOWN);
+
+ g_object_unref (stanza);
+}
+
int
main (int argc, char **argv)
{
@@ -790,6 +851,8 @@ main (int argc, char **argv)
test_extract_errors_extra_application_specific);
g_test_add_func ("/xmpp-stanza/errors/legacy-code",
test_extract_errors_legacy_code);
+ g_test_add_func ("/xmpp-stanza/errors/unrecognised-condition",
+ test_extract_errors_unrecognised_condition);
g_test_add_func ("/xmpp-stanza/errors/no-sense",
test_extract_errors_no_sense);
g_test_add_func ("/xmpp-stanza/errors/not-really",
@@ -799,6 +862,13 @@ main (int argc, char **argv)
g_test_add_func ("/xmpp-stanza/errors/stanza-to-node-jingle",
test_stanza_error_to_node_jingle);
+ g_test_add_data_func ("/xmpp-stanza/types/unknown-stanza-type",
+ "this-will-never-be-real\0" WOCKY_XMPP_NS_JABBER_CLIENT,
+ test_unknown);
+ g_test_add_data_func ("/xmpp-stanza/types/wrong-namespaces",
+ "challenge\0this:is:not:the:sasl:namespace",
+ test_unknown);
+
result = g_test_run ();
test_deinit ();
return result;
diff --git a/tests/wocky-test-connector-server.c b/tests/wocky-test-connector-server.c
index 5f0bfc2..919fa86 100644
--- a/tests/wocky-test-connector-server.c
+++ b/tests/wocky-test-connector-server.c
@@ -82,6 +82,8 @@ static struct { CertSet set; const gchar *key; const gchar *crt; } certs[] =
{ CERT_UNKNOWN, TLS_UNKNOWN_KEY_FILE, TLS_UNKNOWN_CRT_FILE },
{ CERT_SELFSIGN, TLS_SS_KEY_FILE, TLS_SS_CRT_FILE },
{ CERT_REVOKED, TLS_REV_KEY_FILE, TLS_REV_CRT_FILE },
+ { CERT_WILDCARD, TLS_WILD_KEY_FILE, TLS_WILD_CRT_FILE },
+ { CERT_BADWILD, TLS_BADWILD_KEY_FILE, TLS_BADWILD_CRT_FILE },
{ CERT_NONE, NULL, NULL } };
struct _TestConnectorServerPrivate
diff --git a/tests/wocky-test-connector-server.h b/tests/wocky-test-connector-server.h
index 8c74e93..15169d4 100644
--- a/tests/wocky-test-connector-server.h
+++ b/tests/wocky-test-connector-server.h
@@ -120,6 +120,8 @@ typedef enum
CERT_UNKNOWN,
CERT_SELFSIGN,
CERT_REVOKED,
+ CERT_WILDCARD,
+ CERT_BADWILD,
CERT_NONE,
} CertSet;
diff --git a/tests/wocky-test-helper.c b/tests/wocky-test-helper.c
index 9cf0775..629ff6f 100644
--- a/tests/wocky-test-helper.c
+++ b/tests/wocky-test-helper.c
@@ -299,6 +299,7 @@ test_init (int argc,
#endif
g_test_init (&argc, &argv, NULL);
+ g_test_bug_base ("http://bugs.freedesktop.org/show_bug.cgi?id=");
g_type_init ();
wocky_init ();
}
diff --git a/tests/wocky-xmpp-node-test.c b/tests/wocky-xmpp-node-test.c
index 3498ff5..4f19fac 100644
--- a/tests/wocky-xmpp-node-test.c
+++ b/tests/wocky-xmpp-node-test.c
@@ -375,6 +375,35 @@ test_node_iteration (void)
}
static void
+test_node_iter_remove (void)
+{
+ WockyNode *top, *child;
+ WockyNodeTree *tree = wocky_node_tree_new ("foo", "wocky:test",
+ '*', &top,
+ '(', "remove-me", ')',
+ '(', "remove-me", ')',
+ '(', "preserve-me", ')',
+ '(', "remove-me", ')',
+ '(', "preserve-me", ')',
+ '(', "remove-me", ')',
+ '(', "remove-me", ')',
+ NULL);
+ WockyNodeIter iter;
+
+ wocky_node_iter_init (&iter, top, "remove-me", NULL);
+ while (wocky_node_iter_next (&iter, NULL))
+ wocky_node_iter_remove (&iter);
+
+ g_assert_cmpuint (g_slist_length (top->children), ==, 2);
+
+ wocky_node_iter_init (&iter, top, NULL, NULL);
+ while (wocky_node_iter_next (&iter, &child))
+ g_assert_cmpstr (child->name, ==, "preserve-me");
+
+ g_object_unref (tree);
+}
+
+static void
test_get_first_child (void)
{
WockyNodeTree *tree = wocky_node_tree_new ("my-elixir", "my:poison",
@@ -421,6 +450,7 @@ main (int argc, char **argv)
g_test_add_func ("/xmpp-node/append-content-n", test_append_content_n);
g_test_add_func ("/xmpp-node/set-attribute-ns", test_set_attribute_ns);
g_test_add_func ("/xmpp-node/node-iterator", test_node_iteration);
+ g_test_add_func ("/xmpp-node/node-iterator-remove", test_node_iter_remove);
g_test_add_func ("/xmpp-node/get-first-child", test_get_first_child);
result = g_test_run ();
diff --git a/tests/wocky-xmpp-reader-test.c b/tests/wocky-xmpp-reader-test.c
index 80bb0da..610de0f 100644
--- a/tests/wocky-xmpp-reader-test.c
+++ b/tests/wocky-xmpp-reader-test.c
@@ -381,6 +381,71 @@ test_non_character_codepoints (void)
NON_CHARACTER_CODEPOINTS_REPLACEMENT);
}
+static void
+check_namespaces (
+ WockyXmppReader *reader,
+ const gchar *xml,
+ const gchar *expected_namespace)
+{
+ WockyStanza *stanza;
+ WockyNode *top_node, *body_node;
+
+ wocky_xmpp_reader_push (reader, (const guint8 *) xml, strlen (xml));
+ stanza = wocky_xmpp_reader_pop_stanza (reader);
+ g_assert (stanza != NULL);
+ top_node = wocky_stanza_get_top_node (stanza);
+ g_assert (top_node != NULL);
+ g_assert_cmpstr (expected_namespace, ==, wocky_node_get_ns (top_node));
+ body_node = wocky_node_get_first_child (top_node);
+ g_assert (body_node != NULL);
+ g_assert_cmpstr (expected_namespace, ==, wocky_node_get_ns (body_node));
+
+ g_object_unref (stanza);
+ wocky_xmpp_reader_reset (reader);
+}
+
+static void
+test_no_stream_default_namespace (
+ WockyXmppReader *reader,
+ const gchar *expected_default_namespace)
+{
+ /* Regardless of the reader's default namespace, a root node with an
+ * explicitly-specified namespace should get that namespace.
+ */
+ check_namespaces (reader,
+ "<message xmlns='" WOCKY_XMPP_NS_JABBER_CLIENT "'>"
+ "<body>hai</body></message>",
+ WOCKY_XMPP_NS_JABBER_CLIENT);
+
+ /* What namespace the nodes here end up in depends on the reader's default.
+ */
+ check_namespaces (reader, "<message><body>hai</body></message>",
+ expected_default_namespace);
+}
+
+static void
+test_no_stream_default_default_namespace (void)
+{
+ WockyXmppReader *reader = wocky_xmpp_reader_new_no_stream ();
+
+ /* WockyXmppReader defaults to the empty namespace. */
+ test_no_stream_default_namespace (reader, "");
+
+ g_object_unref (reader);
+}
+
+static void
+test_no_stream_specified_default_namespace (void)
+{
+#define WEIRD "wocky:weird:namespace"
+ WockyXmppReader *reader = wocky_xmpp_reader_new_no_stream_ns (WEIRD);
+
+ test_no_stream_default_namespace (reader, WEIRD);
+
+ g_object_unref (reader);
+#undef WEIRD
+}
+
int
main (int argc,
char **argv)
@@ -402,6 +467,10 @@ main (int argc,
g_test_add_func ("/xmpp-reader/whitespace-only", test_whitespace_only);
g_test_add_func ("/xmpp-reader/utf-non-character-codepoints",
test_non_character_codepoints);
+ g_test_add_func ("/xmpp-reader/no-stream-default-default-namespace",
+ test_no_stream_default_default_namespace);
+ g_test_add_func ("/xmpp-reader/no-stream-specified-default-namespace",
+ test_no_stream_specified_default_namespace);
result = g_test_run ();
test_deinit ();
diff --git a/wocky/Makefile.am b/wocky/Makefile.am
index 603519e..fde51ba 100644
--- a/wocky/Makefile.am
+++ b/wocky/Makefile.am
@@ -13,30 +13,28 @@ endif
EXTRA_DIST =
-built_headers = \
- wocky-auth-registry-enumtypes.h \
- wocky-connector-enumtypes.h \
- wocky-data-form-enumtypes.h \
- wocky-muc-enumtypes.h \
- wocky-pubsub-node-enumtypes.h \
- wocky-pubsub-service-enumtypes.h \
+enumtype_sources = \
+ wocky-auth-registry.h \
+ wocky-connector.h \
+ wocky-data-form.h \
+ wocky-jingle-info-internal.h \
+ wocky-jingle-types.h \
+ wocky-muc.h \
+ wocky-pubsub-node.h \
+ wocky-pubsub-service.h \
wocky-signals-marshal.h \
- wocky-tls-enumtypes.h \
- wocky-xmpp-error-enumtypes.h \
- wocky-xmpp-reader-enumtypes.h
+ wocky-tls.h \
+ wocky-xmpp-error.h \
+ wocky-xmpp-reader.h
+
+built_headers = \
+ wocky-enumtypes.h \
+ wocky-signals-marshal.h
built_sources = \
- wocky-auth-registry-enumtypes.c \
- wocky-connector-enumtypes.c \
- wocky-data-form-enumtypes.c \
- wocky-muc-enumtypes.c \
- wocky-pubsub-node-enumtypes.c \
- wocky-pubsub-service-enumtypes.c \
+ wocky-enumtypes.c \
wocky-signals-marshal.c \
- wocky-signals-marshal.list \
- wocky-tls-enumtypes.c \
- wocky-xmpp-error-enumtypes.c \
- wocky-xmpp-reader-enumtypes.c
+ wocky-signals-marshal.list
BUILT_SOURCES = $(built_headers) $(built_sources)
@@ -68,9 +66,21 @@ handwritten_headers = \
wocky-data-form.h \
wocky-debug.h \
wocky-disco-identity.h \
+ wocky-google-relay.h \
wocky-jabber-auth.h \
wocky-jabber-auth-digest.h \
wocky-jabber-auth-password.h \
+ wocky-jingle-content.h \
+ wocky-jingle-factory.h \
+ wocky-jingle-info-internal.h \
+ wocky-jingle-info.h \
+ wocky-jingle-media-rtp.h \
+ wocky-jingle-session.h \
+ wocky-jingle-transport-google.h \
+ wocky-jingle-transport-iceudp.h \
+ wocky-jingle-transport-iface.h \
+ wocky-jingle-transport-rawudp.h \
+ wocky-jingle-types.h \
wocky-ll-connector.h \
wocky-ll-contact.h \
wocky-loopback-stream.h \
@@ -125,9 +135,19 @@ handwritten_sources = \
wocky-disco-identity.c \
wocky-heartbeat-source.c \
wocky-heartbeat-source.h \
+ wocky-google-relay.c \
wocky-jabber-auth.c \
wocky-jabber-auth-digest.c \
wocky-jabber-auth-password.c \
+ wocky-jingle-content.c \
+ wocky-jingle-factory.c \
+ wocky-jingle-info.c \
+ wocky-jingle-media-rtp.c \
+ wocky-jingle-session.c \
+ wocky-jingle-transport-google.c \
+ wocky-jingle-transport-iceudp.c \
+ wocky-jingle-transport-iface.c \
+ wocky-jingle-transport-rawudp.c \
wocky-ll-connector.c \
wocky-ll-contact.c \
wocky-loopback-stream.c \
@@ -182,6 +202,11 @@ libwocky_la_SOURCES = $(handwritten_sources) $(built_sources) \
if INSTALL_HEADERS
libwocky_la_HEADERS = $(handwritten_headers) $(built_headers)
libwocky_ladir = $(HEADER_DIR)/wocky
+
+if ENABLE_SHARED_SUFFIX
+ pkgconfigdir = ${libdir}/pkgconfig
+ pkgconfig_DATA = wocky.pc
+endif
endif
# Coding style checks
@@ -222,6 +247,7 @@ wocky-signals-marshal.list: $(handwritten_sources) Makefile.am
AM_CFLAGS = $(ERROR_CFLAGS) $(GCOV_CFLAGS) \
@GLIB_CFLAGS@ @LIBXML2_CFLAGS@ @SQLITE_CFLAGS@ @TLS_CFLAGS@ \
@LIBIPHB_CFLAGS@ \
+ @SOUP_CFLAGS@ \
-DG_LOG_DOMAIN=\"wocky\" \
-DWOCKY_COMPILATION
@@ -233,31 +259,27 @@ libwocky_la_LIBADD = \
@SQLITE_LIBS@ \
@TLS_LIBS@ \
@LIBIPHB_LIBS@ \
+ @SOUP_LIBS@ \
$(NULL)
# rules for making the glib enum objects
-# The shell variable $long is something like WOCKY_XMPP_ERROR for
-# wocky-xmpp-error-enumtypes.h, and $short is WOCKY.
-$(filter %-enumtypes.h,$(built_headers)): %-enumtypes.h: %.h Makefile.in
- $(AM_V_GEN)set -e; \
- long=`echo $(notdir $*) | tr [:lower:]- [:upper:]_`; \
- short=$${long%%_*}; \
- guard="__$${long}_ENUM_TYPES_H__"; \
- glib-mkenums \
- --fhead "#ifndef $${guard}\n#define $${guard}\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \
+wocky-enumtypes.h: $(enumtype_sources) Makefile.am
+ $(AM_V_GEN)glib-mkenums \
+ --fhead "#ifndef __WOCKY_ENUM_TYPES_H__\n#define __WOCKY_ENUM_TYPES_H__\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \
--fprod "/* enumerations from \"@filename@\" */\n" \
- --vhead "GType @enum_name@_get_type (void);\n#define $${short}_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n" \
- --ftail "G_END_DECLS\n\n#endif" \
- $< > $@
+ --vhead "GType @enum_name@_get_type (void);\n#define WOCKY_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n" \
+ --ftail "G_END_DECLS\n\n#endif /* __WOCKY_ENUM_TYPES_H__ */" \
+ $(enumtype_sources) > $@
-$(filter %-enumtypes.c,$(built_sources)): %-enumtypes.c: %.h Makefile.in
+wocky-enumtypes.c: $(enumtype_sources) Makefile.am
$(AM_V_GEN)glib-mkenums \
- --fhead "#include <$*.h>\n#include <$*-enumtypes.h>" \
- --fprod "\n/* enumerations from \"@filename@\" */" \
+ --fhead "#include \"config.h\"\n" \
+ --fhead "#include <$*.h>" \
+ --fprod "\n/* enumerations from \"@filename@\" */\n#include \"@filename@\"" \
--vhead "GType\n@enum_name@_get_type (void)\n{\n static GType etype = 0;\n if (etype == 0) {\n static const G@Type@Value values[] = {" \
--vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
--vtail " { 0, NULL, NULL }\n };\n etype = g_@type@_register_static (\"@EnumName@\", values);\n }\n return etype;\n}\n" \
- $< > $@
+ $(enumtype_sources) > $@
Android.mk: Makefile.am $(built_sources) $(built_headers)
androgenizer -:PROJECT wocky -:SHARED wocky -:TAGS eng debug \
diff --git a/wocky/wocky-auth-registry.c b/wocky/wocky-auth-registry.c
index efe65bb..9ff42d1 100644
--- a/wocky/wocky-auth-registry.c
+++ b/wocky/wocky-auth-registry.c
@@ -28,7 +28,7 @@ struct _WockyAuthRegistryPrivate
};
static void wocky_auth_registry_start_auth_async_func (WockyAuthRegistry *self,
- const GSList *mechanisms,
+ GSList *mechanisms,
gboolean allow_plain,
gboolean is_secure_channel,
const gchar *username,
@@ -176,12 +176,11 @@ wocky_auth_registry_new (void)
}
static gboolean
-wocky_auth_registry_has_mechanism (const GSList *list, const gchar *mech) {
- GSList *t;
-
- t = g_slist_find_custom ((GSList *) list, mech, (GCompareFunc) g_strcmp0);
-
- return (t != NULL);
+wocky_auth_registry_has_mechanism (
+ GSList *list,
+ const gchar *mech)
+{
+ return (g_slist_find_custom (list, mech, (GCompareFunc) g_strcmp0) != NULL);
}
WockyAuthRegistryStartData *
@@ -218,7 +217,7 @@ wocky_auth_registry_start_data_free (WockyAuthRegistryStartData *start_data)
static gboolean
wocky_auth_registry_select_handler (WockyAuthRegistry *self,
- const GSList *mechanisms,
+ GSList *mechanisms,
gboolean allow_plain,
const gchar *username,
const gchar *password,
@@ -315,7 +314,7 @@ wocky_auth_registry_select_handler (WockyAuthRegistry *self,
static void
wocky_auth_registry_start_auth_async_func (WockyAuthRegistry *self,
- const GSList *mechanisms,
+ GSList *mechanisms,
gboolean allow_plain,
gboolean is_secure_channel,
const gchar *username,
@@ -372,7 +371,7 @@ wocky_auth_registry_start_auth_async_func (WockyAuthRegistry *self,
void
wocky_auth_registry_start_auth_async (WockyAuthRegistry *self,
- const GSList *mechanisms,
+ GSList *mechanisms,
gboolean allow_plain,
gboolean is_secure_channel,
const gchar *username,
@@ -560,7 +559,7 @@ wocky_auth_registry_add_handler (WockyAuthRegistry *self,
*/
gboolean
wocky_auth_registry_supports_one_of (WockyAuthRegistry *self,
- const GSList *mechanisms,
+ GSList *mechanisms,
gboolean allow_plain)
{
return wocky_auth_registry_select_handler (self, mechanisms,
diff --git a/wocky/wocky-auth-registry.h b/wocky/wocky-auth-registry.h
index 30da931..f5e3f45 100644
--- a/wocky/wocky-auth-registry.h
+++ b/wocky/wocky-auth-registry.h
@@ -9,7 +9,7 @@
#include <glib-object.h>
#include <gio/gio.h>
#include "wocky-auth-handler.h"
-#include "wocky-auth-registry-enumtypes.h"
+#include "wocky-enumtypes.h"
G_BEGIN_DECLS
@@ -142,7 +142,7 @@ typedef struct _WockyAuthRegistryPrivate WockyAuthRegistryPrivate;
* supports and gets the initial data from the chosen handler.
*/
typedef void (*WockyAuthRegistryStartAuthAsyncFunc) (WockyAuthRegistry *self,
- const GSList *mechanisms,
+ GSList *mechanisms,
gboolean allow_plain,
gboolean is_secure_channel,
const gchar *username,
@@ -281,7 +281,7 @@ GType wocky_auth_registry_get_type (void) G_GNUC_CONST;
WockyAuthRegistry *wocky_auth_registry_new (void);
void wocky_auth_registry_start_auth_async (WockyAuthRegistry *self,
- const GSList *mechanisms,
+ GSList *mechanisms,
gboolean allow_plain,
gboolean is_secure_channel,
const gchar *username,
@@ -331,7 +331,7 @@ void wocky_auth_registry_failure (WockyAuthRegistry *self,
GError *error);
gboolean wocky_auth_registry_supports_one_of (WockyAuthRegistry *self,
- const GSList *mechanisms,
+ GSList *mechanisms,
gboolean allow_plain);
G_END_DECLS
diff --git a/wocky/wocky-c2s-porter.c b/wocky/wocky-c2s-porter.c
index ec89c4b..8f0daa0 100644
--- a/wocky/wocky-c2s-porter.c
+++ b/wocky/wocky-c2s-porter.c
@@ -165,14 +165,18 @@ typedef enum {
MATCH_JID
} SenderMatch;
+typedef struct {
+ gchar *node;
+ gchar *domain;
+ gchar *resource;
+} JidTriple;
+
typedef struct
{
WockyStanzaType type;
WockyStanzaSubType sub_type;
SenderMatch sender_match;
- gchar *node;
- gchar *domain;
- gchar *resource;
+ JidTriple jid;
guint priority;
WockyStanza *match;
WockyPorterHandlerFunc callback;
@@ -184,7 +188,7 @@ stanza_handler_new (
WockyStanzaType type,
WockyStanzaSubType sub_type,
SenderMatch sender_match,
- const gchar *from,
+ JidTriple *jid,
guint priority,
WockyStanza *stanza,
WockyPorterHandlerFunc callback,
@@ -204,16 +208,13 @@ stanza_handler_new (
if (sender_match == MATCH_JID)
{
- gboolean from_valid;
+ g_assert (jid != NULL);
- g_assert (from != NULL);
- from_valid = wocky_decode_jid (from, &(result->node),
- &(result->domain), &(result->resource));
- g_assert (from_valid);
+ result->jid = *jid;
}
else
{
- g_assert (from == NULL);
+ g_assert (jid == NULL);
}
return result;
@@ -222,9 +223,9 @@ stanza_handler_new (
static void
stanza_handler_free (StanzaHandler *handler)
{
- g_free (handler->node);
- g_free (handler->domain);
- g_free (handler->resource);
+ g_free (handler->jid.node);
+ g_free (handler->jid.domain);
+ g_free (handler->jid.resource);
if (handler->match != NULL)
g_object_unref (handler->match);
@@ -992,17 +993,17 @@ handle_stanza (WockyC2SPorter *self,
break;
case MATCH_JID:
- g_assert (handler->domain != NULL);
+ g_assert (handler->jid.domain != NULL);
- if (wocky_strdiff (node, handler->node))
+ if (wocky_strdiff (node, handler->jid.node))
continue;
- if (wocky_strdiff (domain, handler->domain))
+ if (wocky_strdiff (domain, handler->jid.domain))
continue;
/* If a resource was specified, we need to match against it. */
- if (handler->resource != NULL &&
- wocky_strdiff (resource, handler->resource))
+ if (handler->jid.resource != NULL &&
+ wocky_strdiff (resource, handler->jid.resource))
continue;
break;
@@ -1087,19 +1088,17 @@ is_stanza_important (WockyC2SPorter *self,
WockyC2SPorterPrivate *priv = self->priv;
WockyNode *node = wocky_stanza_get_top_node (stanza);
WockyStanzaType type;
+ WockyStanzaSubType sub_type;
GList *l;
- wocky_stanza_get_type_info (stanza, &type, NULL);
+ wocky_stanza_get_type_info (stanza, &type, &sub_type);
/* <presence/> and <presence type="unavailable"/> are queueable */
- if (type == WOCKY_STANZA_TYPE_PRESENCE)
+ if (type == WOCKY_STANZA_TYPE_PRESENCE &&
+ (sub_type == WOCKY_STANZA_SUB_TYPE_NONE ||
+ sub_type == WOCKY_STANZA_SUB_TYPE_UNAVAILABLE))
{
- const gchar *ptype = wocky_node_get_attribute (node, "type");
- /* presence type is either missing or "unavailable" */
- if ((ptype == NULL) || !wocky_strdiff (ptype, "unavailable"))
- {
- return FALSE;
- }
+ return FALSE;
}
if (priv->queueable_stanza_patterns.length == 0)
@@ -1526,7 +1525,7 @@ wocky_c2s_porter_register_handler_internal (WockyC2SPorter *self,
WockyStanzaType type,
WockyStanzaSubType sub_type,
SenderMatch sender_match,
- const gchar *from,
+ JidTriple *jid,
guint priority,
WockyPorterHandlerFunc callback,
gpointer user_data,
@@ -1537,7 +1536,7 @@ wocky_c2s_porter_register_handler_internal (WockyC2SPorter *self,
g_return_val_if_fail (WOCKY_IS_PORTER (self), 0);
- handler = stanza_handler_new (type, sub_type, sender_match, from, priority,
+ handler = stanza_handler_new (type, sub_type, sender_match, jid, priority,
stanza, callback, user_data);
g_hash_table_insert (priv->handlers_by_id,
@@ -1559,11 +1558,20 @@ wocky_c2s_porter_register_handler_from_by_stanza (WockyPorter *porter,
WockyStanza *stanza)
{
WockyC2SPorter *self = WOCKY_C2S_PORTER (porter);
+ JidTriple jid;
+ gboolean from_valid;
g_return_val_if_fail (from != NULL, 0);
+ from_valid = wocky_decode_jid (from, &jid.node, &jid.domain, &jid.resource);
+ if (!from_valid)
+ {
+ g_critical ("from='%s' isn't a valid JID", from);
+ return 0;
+ }
+
return wocky_c2s_porter_register_handler_internal (self, type, sub_type,
- MATCH_JID, from,
+ MATCH_JID, &jid,
priority, callback, user_data, stanza);
}
diff --git a/wocky/wocky-connector.c b/wocky/wocky-connector.c
index 133b9fd..a35a24b 100644
--- a/wocky/wocky-connector.c
+++ b/wocky/wocky-connector.c
@@ -26,7 +26,7 @@
*
* See: RFC3920 XEP-0077
*
- * Sends and receives #WockyStanzas from an underlying #GIOStream.
+ * Sends and receives #WockyStanza<!-- -->s from an underlying #GIOStream.
* negotiating TLS if possible and completing authentication with the server
* by the "most suitable" method available.
* Returns a #WockyXmppConnection object to the user on successful completion.
@@ -114,6 +114,13 @@
G_DEFINE_TYPE (WockyConnector, wocky_connector, G_TYPE_OBJECT);
+enum {
+ CONNECTION_ESTABLISHED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
static void wocky_connector_class_init (WockyConnectorClass *klass);
/* XMPP connect/auth/etc handlers */
@@ -296,35 +303,43 @@ struct _WockyConnectorPrivate
};
/* choose an appropriate chunk of text describing our state for debug/error */
-static char *
-state_message (WockyConnectorPrivate *priv, const char *str)
+static const gchar *
+state_message (WockyConnectorPrivate *priv)
{
- const char *state = NULL;
-
if (priv->authed)
- state = "Authentication Completed";
+ return "Authentication Completed";
else if (priv->encrypted)
{
if (priv->legacy_ssl)
- state = "SSL Negotiated";
+ return "SSL Negotiated";
else
- state = "TLS Negotiated";
+ return "TLS Negotiated";
}
else if (priv->connected)
- state = "TCP Connection Established";
+ return "TCP Connection Established";
else
- state = "Connecting... ";
+ return "Connecting... ";
+}
- return g_strdup_printf ("%s: %s", state, str);
+static void
+complete_operation (WockyConnector *connector)
+{
+ WockyConnectorPrivate *priv = connector->priv;
+ GSimpleAsyncResult *tmp;
+
+ tmp = priv->result;
+ priv->result = NULL;
+ g_simple_async_result_complete (tmp);
+ g_object_unref (tmp);
}
+
static void
abort_connect_error (WockyConnector *connector,
GError **error,
const char *fmt,
...)
{
- GSimpleAsyncResult *tmp = NULL;
WockyConnectorPrivate *priv = NULL;
va_list args;
@@ -356,18 +371,14 @@ abort_connect_error (WockyConnector *connector,
priv->cancellable = NULL;
}
- tmp = priv->result;
- priv->result = NULL;
- g_simple_async_result_set_from_error (tmp, *error);
- g_simple_async_result_complete (tmp);
- g_object_unref (tmp);
+ g_simple_async_result_set_from_error (priv->result, *error);
+ complete_operation (connector);
}
static void
abort_connect (WockyConnector *connector,
GError *error)
{
- GSimpleAsyncResult *tmp = NULL;
WockyConnectorPrivate *priv = connector->priv;
if (priv->sock != NULL)
@@ -383,11 +394,8 @@ abort_connect (WockyConnector *connector,
priv->cancellable = NULL;
}
- tmp = priv->result;
- priv->result = NULL;
- g_simple_async_result_set_from_error (tmp, error);
- g_simple_async_result_complete (tmp);
- g_object_unref (tmp);
+ g_simple_async_result_set_from_error (priv->result, error);
+ complete_operation (connector);
}
static void
@@ -463,7 +471,7 @@ wocky_connector_set_property (GObject *object,
*g_value_get_string (value) != '\0')
priv->resource = g_value_dup_string (value);
else
- priv->resource = g_strdup_printf ("Wocky_%x", rand());
+ priv->resource = NULL;
break;
case PROP_XMPP_PORT:
priv->xmpp_port = g_value_get_uint (value);
@@ -641,9 +649,9 @@ wocky_connector_class_init (WockyConnectorClass *klass)
/**
* WockyConnector:resource:
*
- * The resource (sans '/') for this connection. Will be generated
- * automatically if not set. May be altered by the server anyway
- * upon successful binding.
+ * The resource (sans '/') for this connection. If %NULL or the empty string,
+ * Wocky will let the server decide. Even if you specify a particular
+ * resource, the server may modify it.
*/
spec = g_param_spec_string ("resource", "resource",
"XMPP resource to append to the jid", NULL,
@@ -756,9 +764,28 @@ wocky_connector_class_init (WockyConnectorClass *klass)
"TLS Handler", WOCKY_TYPE_TLS_HANDLER,
(G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (oclass, PROP_TLS_HANDLER, spec);
+
+ /**
+ * WockyConnector::connection-established:
+ * @connection: the #GSocketConnection
+ *
+ * Emitted as soon as a connection to the remote server has been
+ * established. This can be useful if you want to do something
+ * unusual to the connection early in its lifetime not supported by
+ * the #WockyConnector APIs.
+ *
+ * As the connection process has only just started and the stream
+ * not even opened yet, no data must be sent over @connection. This
+ * signal is merely intended to set esoteric socket options (such as
+ * TCP_NODELAY) on the connection.
+ */
+ signals[CONNECTION_ESTABLISHED] = g_signal_new ("connection-established",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, G_TYPE_SOCKET_CONNECTION);
}
-#define UNREF_AND_FORGET(x) if (x != NULL) { g_object_unref (x); x = NULL; }
#define GFREE_AND_FORGET(x) g_free (x); x = NULL;
static void
@@ -771,12 +798,12 @@ wocky_connector_dispose (GObject *object)
return;
priv->dispose_has_run = TRUE;
- UNREF_AND_FORGET (priv->conn);
- UNREF_AND_FORGET (priv->client);
- UNREF_AND_FORGET (priv->sock);
- UNREF_AND_FORGET (priv->features);
- UNREF_AND_FORGET (priv->auth_registry);
- UNREF_AND_FORGET (priv->tls_handler);
+ g_clear_object (&priv->conn);
+ g_clear_object (&priv->client);
+ g_clear_object (&priv->sock);
+ g_clear_object (&priv->features);
+ g_clear_object (&priv->auth_registry);
+ g_clear_object (&priv->tls_handler);
if (G_OBJECT_CLASS (wocky_connector_parent_class )->dispose)
G_OBJECT_CLASS (wocky_connector_parent_class)->dispose (object);
@@ -912,6 +939,9 @@ tcp_srv_connected (GObject *source,
else
{
DEBUG ("SRV connection succeeded");
+
+ g_signal_emit (self, signals[CONNECTION_ESTABLISHED], 0, priv->sock);
+
priv->connected = TRUE;
priv->state = WCON_TCP_CONNECTED;
maybe_old_ssl (self);
@@ -952,6 +982,9 @@ tcp_host_connected (GObject *source,
else
{
DEBUG ("HOST connection succeeded");
+
+ g_signal_emit (self, signals[CONNECTION_ESTABLISHED], 0, priv->sock);
+
priv->connected = TRUE;
priv->state = WCON_TCP_CONNECTED;
maybe_old_ssl (self);
@@ -1104,7 +1137,6 @@ xmpp_init_recv_cb (GObject *source,
GError *error = NULL;
WockyConnector *self = WOCKY_CONNECTOR (data);
WockyConnectorPrivate *priv = self->priv;
- gchar *debug = NULL;
gchar *version = NULL;
gchar *from = NULL;
gchar *id = NULL;
@@ -1113,9 +1145,8 @@ xmpp_init_recv_cb (GObject *source,
if (!wocky_xmpp_connection_recv_open_finish (priv->conn, result, NULL,
&from, &version, NULL, &id, &error))
{
- char *msg = state_message (priv, error->message);
- abort_connect_error (self, &error, msg);
- g_free (msg);
+ abort_connect_error (self, &error, "%s: %s",
+ state_message (priv), error->message);
g_error_free (error);
goto out;
}
@@ -1123,10 +1154,9 @@ xmpp_init_recv_cb (GObject *source,
g_free (priv->session_id);
priv->session_id = g_strdup (id);
- debug = state_message (priv, "");
- DEBUG ("%s: received XMPP version=%s stream open from server", debug,
+ DEBUG ("%s: received XMPP version=%s stream open from server",
+ state_message (priv),
version != NULL ? version : "(unspecified)");
- g_free (debug);
ver = (version != NULL) ? atof (version) : -1;
@@ -1228,17 +1258,16 @@ xmpp_features_cb (GObject *source,
if (stream_error_abort (self, stanza))
goto out;
- DEBUG ("received feature stanza from server");
- node = wocky_stanza_get_top_node (stanza);
-
- if (!wocky_node_matches (node, "features", WOCKY_XMPP_NS_STREAM))
+ if (!wocky_stanza_has_type (stanza, WOCKY_STANZA_TYPE_STREAM_FEATURES))
{
- char *msg = state_message (priv, "Malformed or missing feature stanza");
- abort_connect_code (data, WOCKY_CONNECTOR_ERROR_BAD_FEATURES, msg);
- g_free (msg);
+ abort_connect_code (data, WOCKY_CONNECTOR_ERROR_BAD_FEATURES, "%s: %s",
+ state_message (priv), "Malformed or missing feature stanza");
goto out;
}
+ DEBUG ("received feature stanza from server");
+ node = wocky_stanza_get_top_node (stanza);
+
/* cache the current feature set: according to the RFC, we should forget
* any previous feature set as soon as we open a new stream, so that
* happens elsewhere */
@@ -1545,7 +1574,7 @@ xep77_cancel_recv (GObject *source,
g_object_unref (priv->cancellable);
priv->cancellable = NULL;
}
- g_simple_async_result_complete (priv->result);
+ complete_operation (self);
priv->state = WCON_DISCONNECTED;
}
@@ -2044,17 +2073,13 @@ establish_session (WockyConnector *self)
}
else
{
- GSimpleAsyncResult *tmp = priv->result;
-
if (priv->cancellable != NULL)
{
g_object_unref (priv->cancellable);
priv->cancellable = NULL;
}
- priv->result = NULL;
- g_simple_async_result_complete (tmp);
- g_object_unref (tmp);
+ complete_operation (self);
}
}
@@ -2114,7 +2139,6 @@ establish_session_recv_cb (GObject *source,
switch (sub)
{
WockyConnectorError code;
- GSimpleAsyncResult *tmp;
case WOCKY_STANZA_SUB_TYPE_ERROR:
wocky_stanza_extract_errors (reply, NULL, &error, NULL, NULL);
@@ -2154,9 +2178,7 @@ establish_session_recv_cb (GObject *source,
priv->cancellable = NULL;
}
- tmp = priv->result;
- g_simple_async_result_complete (tmp);
- g_object_unref (tmp);
+ complete_operation (self);
}
break;
diff --git a/wocky/wocky-connector.h b/wocky/wocky-connector.h
index 3d5d600..ef543d5 100644
--- a/wocky/wocky-connector.h
+++ b/wocky/wocky-connector.h
@@ -26,7 +26,7 @@
#include <glib-object.h>
-#include "wocky-connector-enumtypes.h"
+#include "wocky-enumtypes.h"
#include "wocky-sasl-auth.h"
#include "wocky-xmpp-connection.h"
#include "wocky-stanza.h"
diff --git a/wocky/wocky-data-form.h b/wocky/wocky-data-form.h
index 931c535..9893873 100644
--- a/wocky/wocky-data-form.h
+++ b/wocky/wocky-data-form.h
@@ -26,7 +26,7 @@
#include <glib-object.h>
-#include "wocky-data-form-enumtypes.h"
+#include "wocky-enumtypes.h"
#include "wocky-node.h"
G_BEGIN_DECLS
@@ -88,6 +88,12 @@ struct _WockyDataFormFieldOption
* @desc: the description of the field
* @required: %TRUE if the field is required, otherwise %FALSE
* @default_value: the default of the field
+ * @raw_value_contents: a %NULL-terminated array holding the literal value(s) as
+ * specified in the original XML. For example, this might be something like
+ * <code language="c">{ "1", NULL }</code> or <code language="c">{ "false",
+ * NULL }</code> for a #WOCKY_DATA_FORM_FIELD_TYPE_BOOLEAN field, or
+ * <code language="c">{ "hi", "there", NULL }</code> for a
+ * #WOCKY_DATA_FORM_FIELD_TYPE_TEXT_MULTI field.
* @value: the field value
* @options: a #GSList of #WockyDataFormFieldOption<!-- -->s if @type
* if %WOCKY_DATA_FORM_FIELD_TYPE_LIST_MULTI or
@@ -104,8 +110,6 @@ struct _WockyDataFormField
gchar *desc;
gboolean required;
GValue *default_value;
- /* a GStrv of actual values so can be {"1", NULL} or {"false", NULL}
- * for BOOLEAN or {"hi", "there", NULL} TEXT_MULTI, for example. */
gchar **raw_value_contents;
GValue *value;
/* for LIST_MULTI and LIST_SINGLE only.
diff --git a/wocky/wocky-debug-internal.h b/wocky/wocky-debug-internal.h
index b9c261b..7e3004c 100644
--- a/wocky/wocky-debug-internal.h
+++ b/wocky/wocky-debug-internal.h
@@ -30,6 +30,9 @@ void wocky_debug_stanza (WockyDebugFlags flag, WockyStanza *stanza,
void wocky_debug_node_tree (WockyDebugFlags flag, WockyNodeTree *tree,
const gchar *format, ...)
G_GNUC_PRINTF (3, 4);
+void wocky_debug_node (WockyDebugFlags flag, WockyNode *node,
+ const gchar *format, ...)
+ G_GNUC_PRINTF (3, 4);
#ifdef WOCKY_DEBUG_FLAG
@@ -45,6 +48,10 @@ void wocky_debug_node_tree (WockyDebugFlags flag, WockyNodeTree *tree,
wocky_debug_node_tree (WOCKY_DEBUG_FLAG, tree, "%s: " format, G_STRFUNC,\
##__VA_ARGS__)
+#define DEBUG_NODE(node, format, ...) \
+ wocky_debug_node (WOCKY_DEBUG_FLAG, node, "%s: " format, G_STRFUNC,\
+ ##__VA_ARGS__)
+
#define DEBUGGING wocky_debug_flag_is_set(WOCKY_DEBUG_FLAG)
#endif /* WOCKY_DEBUG_FLAG */
@@ -75,6 +82,13 @@ DEBUG_NODE_TREE (WockyNodeTree *tree,
{
}
+static inline void
+DEBUG_NODE (WockyNode *node,
+ const gchar *format,
+ ...)
+{
+}
+
#define DEBUGGING 0
#endif /* WOCKY_DEBUG_FLAG */
diff --git a/wocky/wocky-debug.c b/wocky/wocky-debug.c
index a333a38..f313dae 100644
--- a/wocky/wocky-debug.c
+++ b/wocky/wocky-debug.c
@@ -36,6 +36,7 @@ static GDebugKey keys[] = {
{ "heartbeat", WOCKY_DEBUG_HEARTBEAT },
{ "presence", WOCKY_DEBUG_PRESENCE },
{ "connection-factory",WOCKY_DEBUG_CONNECTION_FACTORY},
+ { "media", WOCKY_DEBUG_JINGLE },
{ 0, },
};
@@ -88,8 +89,8 @@ void wocky_debug_valist (WockyDebugFlags flag,
}
static void
-wocky_debug_node_tree_va (WockyDebugFlags flag,
- WockyNodeTree *tree,
+wocky_debug_node_va (WockyDebugFlags flag,
+ WockyNode *node,
const gchar *format,
va_list args)
{
@@ -101,8 +102,7 @@ wocky_debug_node_tree_va (WockyDebugFlags flag,
msg = g_strdup_vprintf (format, args);
- node_str = wocky_node_to_string (
- wocky_node_tree_get_top_node (tree));
+ node_str = wocky_node_to_string (node);
g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "%s\n%s", msg, node_str);
@@ -112,6 +112,28 @@ wocky_debug_node_tree_va (WockyDebugFlags flag,
}
void
+wocky_debug_node (WockyDebugFlags flag,
+ WockyNode *node,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+
+ va_start (args, format);
+ wocky_debug_node_va (flag, node, format, args);
+ va_end (args);
+}
+
+static void
+wocky_debug_node_tree_va (WockyDebugFlags flag,
+ WockyNodeTree *tree,
+ const gchar *format,
+ va_list args)
+{
+ wocky_debug_node_va (flag, wocky_node_tree_get_top_node (tree), format, args);
+}
+
+void
wocky_debug_node_tree (WockyDebugFlags flag,
WockyNodeTree *tree,
const gchar *format,
diff --git a/wocky/wocky-debug.h b/wocky/wocky-debug.h
index ba8e4bc..e48fec3 100644
--- a/wocky/wocky-debug.h
+++ b/wocky/wocky-debug.h
@@ -31,6 +31,7 @@ typedef enum
WOCKY_DEBUG_HEARTBEAT = 1 << 18,
WOCKY_DEBUG_PRESENCE = 1 << 19,
WOCKY_DEBUG_CONNECTION_FACTORY= 1 << 20,
+ WOCKY_DEBUG_JINGLE = 1 << 21,
} WockyDebugFlags;
#define WOCKY_DEBUG_XMPP (WOCKY_DEBUG_XMPP_READER | WOCKY_DEBUG_XMPP_WRITER)
diff --git a/wocky/wocky-google-relay.c b/wocky/wocky-google-relay.c
new file mode 100644
index 0000000..589820a
--- /dev/null
+++ b/wocky/wocky-google-relay.c
@@ -0,0 +1,325 @@
+/*
+ * google-relay.c - Support for Google relays for Jingle
+ *
+ * Copyright (C) 2006-2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "wocky-google-relay.h"
+
+#include <string.h>
+
+#ifdef ENABLE_GOOGLE_RELAY
+#include <libsoup/soup.h>
+#endif
+
+#define WOCKY_DEBUG_FLAG WOCKY_DEBUG_JINGLE
+
+#ifdef G_OS_WIN32
+#undef ERROR
+#endif
+
+#include "wocky-debug-internal.h"
+
+#define RELAY_HTTP_TIMEOUT 5
+
+struct _WockyGoogleRelayResolver {
+#ifdef ENABLE_GOOGLE_RELAY
+ SoupSession *soup;
+#else
+ GObject *soup;
+#endif
+};
+
+typedef struct
+{
+ GPtrArray *relays;
+ guint component;
+ guint requests_to_do;
+ WockyJingleInfoRelaySessionCb callback;
+ gpointer user_data;
+} RelaySessionData;
+
+static RelaySessionData *
+relay_session_data_new (guint requests_to_do,
+ WockyJingleInfoRelaySessionCb callback,
+ gpointer user_data)
+{
+ RelaySessionData *rsd = g_slice_new0 (RelaySessionData);
+
+ rsd->relays = g_ptr_array_sized_new (requests_to_do);
+ g_ptr_array_set_free_func (rsd->relays, (GDestroyNotify) wocky_jingle_relay_free);
+ rsd->component = 1;
+ rsd->requests_to_do = requests_to_do;
+ rsd->callback = callback;
+ rsd->user_data = user_data;
+
+ return rsd;
+}
+
+/* This is a GSourceFunc */
+static gboolean
+relay_session_data_call (gpointer p)
+{
+ RelaySessionData *rsd = p;
+
+ g_assert (rsd->callback != NULL);
+
+ rsd->callback (rsd->relays, rsd->user_data);
+
+ return FALSE;
+}
+
+/* This is a GDestroyNotify */
+static void
+relay_session_data_destroy (gpointer p)
+{
+ RelaySessionData *rsd = p;
+
+ g_ptr_array_unref (rsd->relays);
+
+ g_slice_free (RelaySessionData, rsd);
+}
+
+#ifdef ENABLE_GOOGLE_RELAY
+
+static void
+translate_relay_info (GPtrArray *relays,
+ const gchar *relay_ip,
+ const gchar *username,
+ const gchar *password,
+ WockyJingleRelayType relay_type,
+ const gchar *port_string,
+ guint component)
+{
+ guint64 portll;
+ guint port;
+
+ if (port_string == NULL)
+ {
+ DEBUG ("no relay port for %u found", relay_type);
+ return;
+ }
+
+ portll = g_ascii_strtoull (port_string, NULL, 10);
+
+ if (portll == 0 || portll > G_MAXUINT16)
+ {
+ DEBUG ("failed to parse relay port '%s' for %u", port_string,
+ relay_type);
+ return;
+ }
+ port = (guint) portll;
+
+ DEBUG ("type=%u ip=%s port=%u username=%s password=%s component=%u",
+ relay_type, relay_ip, port, username, password, component);
+
+ g_ptr_array_add (relays,
+ wocky_jingle_relay_new (relay_type, relay_ip, port, username, password,
+ component));
+}
+
+static void
+on_http_response (SoupSession *soup,
+ SoupMessage *msg,
+ gpointer user_data)
+{
+ RelaySessionData *rsd = user_data;
+
+ if (msg->status_code != 200)
+ {
+ DEBUG ("Google session creation failed, relaying not used: %d %s",
+ msg->status_code, msg->reason_phrase);
+ }
+ else
+ {
+ /* parse a=b lines into GHashTable
+ * (key, value both borrowed from items of the strv 'lines') */
+ GHashTable *map = g_hash_table_new (g_str_hash, g_str_equal);
+ gchar **lines;
+ guint i;
+ const gchar *relay_ip;
+ const gchar *relay_udp_port;
+ const gchar *relay_tcp_port;
+ const gchar *relay_ssltcp_port;
+ const gchar *username;
+ const gchar *password;
+ gchar *escaped_str;
+
+ escaped_str = g_strescape (msg->response_body->data, "\r\n");
+ DEBUG ("Response from Google:\n====\n%s\n====", escaped_str);
+ g_free (escaped_str);
+
+ lines = g_strsplit (msg->response_body->data, "\n", 0);
+
+ if (lines != NULL)
+ {
+ for (i = 0; lines[i] != NULL; i++)
+ {
+ gchar *delim = strchr (lines[i], '=');
+ size_t len;
+
+ if (delim == NULL || delim == lines[i])
+ {
+ /* ignore empty keys or lines without '=' */
+ continue;
+ }
+
+ len = strlen (lines[i]);
+
+ if (lines[i][len - 1] == '\r')
+ {
+ lines[i][len - 1] = '\0';
+ }
+
+ *delim = '\0';
+ g_hash_table_insert (map, lines[i], delim + 1);
+ }
+ }
+
+ relay_ip = g_hash_table_lookup (map, "relay.ip");
+ relay_udp_port = g_hash_table_lookup (map, "relay.udp_port");
+ relay_tcp_port = g_hash_table_lookup (map, "relay.tcp_port");
+ relay_ssltcp_port = g_hash_table_lookup (map, "relay.ssltcp_port");
+ username = g_hash_table_lookup (map, "username");
+ password = g_hash_table_lookup (map, "password");
+
+ if (relay_ip == NULL)
+ {
+ DEBUG ("No relay.ip found");
+ }
+ else if (username == NULL)
+ {
+ DEBUG ("No username found");
+ }
+ else if (password == NULL)
+ {
+ DEBUG ("No password found");
+ }
+ else
+ {
+ translate_relay_info (rsd->relays, relay_ip, username, password,
+ WOCKY_JINGLE_RELAY_TYPE_UDP, relay_udp_port, rsd->component);
+ translate_relay_info (rsd->relays, relay_ip, username, password,
+ WOCKY_JINGLE_RELAY_TYPE_TCP, relay_tcp_port, rsd->component);
+ translate_relay_info (rsd->relays, relay_ip, username, password,
+ WOCKY_JINGLE_RELAY_TYPE_TLS, relay_ssltcp_port, rsd->component);
+ }
+
+ g_strfreev (lines);
+ g_hash_table_unref (map);
+ }
+
+ rsd->component++;
+
+ if ((--rsd->requests_to_do) == 0)
+ {
+ relay_session_data_call (rsd);
+ relay_session_data_destroy (rsd);
+ }
+}
+
+#endif /* ENABLE_GOOGLE_RELAY */
+
+WockyGoogleRelayResolver *
+wocky_google_relay_resolver_new (void)
+{
+ WockyGoogleRelayResolver *resolver =
+ g_slice_new0 (WockyGoogleRelayResolver);
+
+#ifdef ENABLE_GOOGLE_RELAY
+
+ resolver->soup = soup_session_async_new ();
+
+ /* If we don't get answer in a few seconds, relay won't do
+ * us much help anyways. */
+ g_object_set (resolver->soup, "timeout", RELAY_HTTP_TIMEOUT, NULL);
+
+#endif
+
+ return resolver;
+}
+
+void
+wocky_google_relay_resolver_destroy (WockyGoogleRelayResolver *self)
+{
+ g_clear_object (&self->soup);
+
+ g_slice_free (WockyGoogleRelayResolver, self);
+}
+
+void
+wocky_google_relay_resolver_resolve (WockyGoogleRelayResolver *self,
+ guint components,
+ const gchar *server,
+ guint16 port,
+ const gchar *token,
+ WockyJingleInfoRelaySessionCb callback,
+ gpointer user_data)
+{
+ RelaySessionData *rsd =
+ relay_session_data_new (components, callback, user_data);
+
+#ifdef ENABLE_GOOGLE_RELAY
+
+ gchar *url;
+ guint i;
+
+ if (server == NULL)
+ {
+ DEBUG ("No relay server provided, not creating google relay session");
+ g_idle_add_full (G_PRIORITY_DEFAULT, relay_session_data_call, rsd,
+ relay_session_data_destroy);
+ return;
+ }
+
+ if (token == NULL)
+ {
+ DEBUG ("No relay token provided, not creating google relay session");
+ g_idle_add_full (G_PRIORITY_DEFAULT, relay_session_data_call, rsd,
+ relay_session_data_destroy);
+ return;
+ }
+
+ url = g_strdup_printf ("http://%s:%u/create_session", server, (guint) port);
+
+ for (i = 0; i < components; i++)
+ {
+ SoupMessage *msg = soup_message_new ("GET", url);
+
+ DEBUG ("Trying to create a new relay session on %s", url);
+
+ /* libjingle sets both headers, so shall we */
+ soup_message_headers_append (msg->request_headers,
+ "X-Talk-Google-Relay-Auth", token);
+ soup_message_headers_append (msg->request_headers,
+ "X-Google-Relay-Auth", token);
+
+ soup_session_queue_message (self->soup, msg, on_http_response, rsd);
+ }
+
+ g_free (url);
+
+#else /* !ENABLE_GOOGLE_RELAY */
+
+ DEBUG ("Google relay service is not supported");
+
+ g_idle_add_full (G_PRIORITY_DEFAULT, relay_session_data_call, rsd,
+ relay_session_data_destroy);
+
+#endif
+}
diff --git a/wocky/wocky-google-relay.h b/wocky/wocky-google-relay.h
new file mode 100644
index 0000000..0e3276f
--- /dev/null
+++ b/wocky/wocky-google-relay.h
@@ -0,0 +1,48 @@
+/*
+ * google-relay.h - Header for WockyGoogleRelaySession
+ *
+ * Copyright (C) 2006-2008 Collabora Ltd.
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#if !defined (WOCKY_COMPILATION)
+# error "This is an internal header."
+#endif
+
+#ifndef __WOCKY_GOOGLE_RELAY_H__
+#define __WOCKY_GOOGLE_RELAY_H__
+
+#include <glib.h>
+
+#include "wocky-jingle-info.h"
+
+G_BEGIN_DECLS
+
+typedef struct _WockyGoogleRelayResolver WockyGoogleRelayResolver;
+
+WockyGoogleRelayResolver * wocky_google_relay_resolver_new (void);
+void wocky_google_relay_resolver_destroy (WockyGoogleRelayResolver *self);
+void wocky_google_relay_resolver_resolve (WockyGoogleRelayResolver *self,
+ guint requests_to_do,
+ const gchar *server,
+ guint16 port,
+ const gchar *token,
+ WockyJingleInfoRelaySessionCb callback,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* __WOCKY_GOOGLE_RELAY_H__ */
diff --git a/wocky/wocky-jabber-auth.c b/wocky/wocky-jabber-auth.c
index 7d5cc43..3879872 100644
--- a/wocky/wocky-jabber-auth.c
+++ b/wocky/wocky-jabber-auth.c
@@ -303,7 +303,7 @@ auth_failed (WockyJabberAuth *self, gint code, const gchar *format, ...)
static gboolean
stream_error (WockyJabberAuth *self, WockyStanza *stanza)
{
- WockyStanzaType type = WOCKY_STANZA_TYPE_NONE;
+ GError *error = NULL;
if (stanza == NULL)
{
@@ -311,13 +311,8 @@ stream_error (WockyJabberAuth *self, WockyStanza *stanza)
return TRUE;
}
- wocky_stanza_get_type_info (stanza, &type, NULL);
-
- if (type == WOCKY_STANZA_TYPE_STREAM_ERROR)
+ if (wocky_stanza_extract_stream_error (stanza, &error))
{
- GError *error = wocky_xmpp_stream_error_from_node (
- wocky_stanza_get_top_node (stanza));
-
auth_failed (self, WOCKY_AUTH_ERROR_STREAM, "%s: %s",
wocky_enum_to_nick (WOCKY_TYPE_XMPP_STREAM_ERROR, error->code),
error->message);
diff --git a/wocky/wocky-jingle-content.c b/wocky/wocky-jingle-content.c
new file mode 100644
index 0000000..82eea32
--- /dev/null
+++ b/wocky/wocky-jingle-content.c
@@ -0,0 +1,1423 @@
+/*
+ * wocky-jingle-content.c - Source for WockyJingleContent
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "wocky-jingle-content.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#define WOCKY_DEBUG_FLAG WOCKY_DEBUG_JINGLE
+
+#include "wocky-debug-internal.h"
+#include "wocky-jingle-factory.h"
+#include "wocky-jingle-session.h"
+#include "wocky-jingle-transport-iface.h"
+#include "wocky-jingle-transport-google.h"
+#include "wocky-jingle-media-rtp.h"
+#include "wocky-namespaces.h"
+#include "wocky-signals-marshal.h"
+#include "wocky-utils.h"
+
+/* signal enum */
+enum
+{
+ READY,
+ NEW_CANDIDATES,
+ REMOVED,
+ NEW_SHARE_CHANNEL,
+ COMPLETED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum
+{
+ PROP_SESSION = 1,
+ PROP_CONTENT_NS,
+ PROP_TRANSPORT_NS,
+ PROP_NAME,
+ PROP_SENDERS,
+ PROP_STATE,
+ PROP_DISPOSITION,
+ PROP_LOCALLY_CREATED,
+ LAST_PROPERTY
+};
+
+struct _WockyJingleContentPrivate
+{
+ gchar *name;
+ gchar *creator;
+ gboolean created_by_us;
+ WockyJingleContentState state;
+ WockyJingleContentSenders senders;
+
+ gchar *content_ns;
+ gchar *transport_ns;
+ gchar *disposition;
+
+ WockyJingleTransportIface *transport;
+
+ /* Whether we've got the codecs (intersection) ready. */
+ gboolean media_ready;
+
+ /* Whether we have at least one local candidate. */
+ gboolean have_local_candidates;
+
+ guint gtalk4_event_id;
+ guint last_share_channel_component_id;
+
+ gboolean dispose_has_run;
+};
+
+#define DEFAULT_CONTENT_TIMEOUT 60000
+
+/* lookup tables */
+
+G_DEFINE_TYPE(WockyJingleContent, wocky_jingle_content, G_TYPE_OBJECT);
+
+static void new_transport_candidates_cb (WockyJingleTransportIface *trans,
+ GList *candidates, WockyJingleContent *content);
+static void _maybe_ready (WockyJingleContent *self);
+static void transport_created (WockyJingleContent *c);
+
+static void
+wocky_jingle_content_init (WockyJingleContent *obj)
+{
+ WockyJingleContentPrivate *priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (obj, WOCKY_TYPE_JINGLE_CONTENT,
+ WockyJingleContentPrivate);
+ obj->priv = priv;
+
+ DEBUG ("%p", obj);
+
+ priv->state = WOCKY_JINGLE_CONTENT_STATE_EMPTY;
+ priv->created_by_us = TRUE;
+ priv->media_ready = FALSE;
+ priv->have_local_candidates = FALSE;
+ priv->gtalk4_event_id = 0;
+ priv->dispose_has_run = FALSE;
+
+ obj->session = NULL;
+}
+
+static void
+wocky_jingle_content_dispose (GObject *object)
+{
+ WockyJingleContent *content = WOCKY_JINGLE_CONTENT (object);
+ WockyJingleContentPrivate *priv = content->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ DEBUG ("%p", object);
+ priv->dispose_has_run = TRUE;
+
+ if (priv->gtalk4_event_id != 0)
+ {
+ g_source_remove (priv->gtalk4_event_id);
+ priv->gtalk4_event_id = 0;
+ }
+
+ g_free (priv->name);
+ priv->name = NULL;
+
+ g_free (priv->creator);
+ priv->creator = NULL;
+
+ g_free (priv->content_ns);
+ priv->content_ns = NULL;
+
+ g_free (priv->transport_ns);
+ priv->transport_ns = NULL;
+
+ g_free (priv->disposition);
+ priv->disposition = NULL;
+
+ if (G_OBJECT_CLASS (wocky_jingle_content_parent_class)->dispose)
+ G_OBJECT_CLASS (wocky_jingle_content_parent_class)->dispose (object);
+}
+
+static void
+wocky_jingle_content_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleContent *self = WOCKY_JINGLE_CONTENT (object);
+ WockyJingleContentPrivate *priv = self->priv;
+
+ switch (property_id) {
+ case PROP_SESSION:
+ g_value_set_object (value, self->session);
+ break;
+ case PROP_NAME:
+ g_value_set_string (value, priv->name);
+ break;
+ case PROP_SENDERS:
+ g_value_set_uint (value, priv->senders);
+ break;
+ case PROP_STATE:
+ g_value_set_uint (value, priv->state);
+ break;
+ case PROP_CONTENT_NS:
+ g_value_set_string (value, priv->content_ns);
+ break;
+ case PROP_TRANSPORT_NS:
+ g_value_set_string (value, priv->transport_ns);
+ break;
+ case PROP_DISPOSITION:
+ g_value_set_string (value, priv->disposition);
+ break;
+ case PROP_LOCALLY_CREATED:
+ g_value_set_boolean (value, priv->created_by_us);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_jingle_content_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleContent *self = WOCKY_JINGLE_CONTENT (object);
+ WockyJingleContentPrivate *priv = self->priv;
+
+ switch (property_id) {
+ case PROP_SESSION:
+ self->session = g_value_get_object (value);
+ break;
+ case PROP_CONTENT_NS:
+ g_free (priv->content_ns);
+ priv->content_ns = g_value_dup_string (value);
+ break;
+ case PROP_TRANSPORT_NS:
+ g_free (priv->transport_ns);
+ priv->transport_ns = g_value_dup_string (value);
+
+ /* We can't switch transports. */
+ g_assert (priv->transport == NULL);
+
+ if (priv->transport_ns != NULL)
+ {
+ GType transport_type = wocky_jingle_factory_lookup_transport (
+ wocky_jingle_session_get_factory (self->session),
+ priv->transport_ns);
+
+ g_assert (transport_type != 0);
+
+ priv->transport = wocky_jingle_transport_iface_new (transport_type,
+ self, priv->transport_ns);
+
+ g_signal_connect (priv->transport, "new-candidates",
+ (GCallback) new_transport_candidates_cb, self);
+
+ transport_created (self);
+ }
+ break;
+ case PROP_NAME:
+ /* can't rename */
+ g_assert (priv->name == NULL);
+
+ priv->name = g_value_dup_string (value);
+ break;
+ case PROP_SENDERS:
+ priv->senders = g_value_get_uint (value);
+ break;
+ case PROP_STATE:
+ priv->state = g_value_get_uint (value);
+ break;
+ case PROP_DISPOSITION:
+ g_assert (priv->disposition == NULL);
+ priv->disposition = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static WockyJingleContentSenders
+get_default_senders_real (WockyJingleContent *c)
+{
+ return WOCKY_JINGLE_CONTENT_SENDERS_BOTH;
+}
+
+
+static void
+wocky_jingle_content_class_init (WockyJingleContentClass *cls)
+{
+ GParamSpec *param_spec;
+ GObjectClass *object_class = G_OBJECT_CLASS (cls);
+
+ g_type_class_add_private (cls, sizeof (WockyJingleContentPrivate));
+
+ object_class->get_property = wocky_jingle_content_get_property;
+ object_class->set_property = wocky_jingle_content_set_property;
+ object_class->dispose = wocky_jingle_content_dispose;
+
+ cls->get_default_senders = get_default_senders_real;
+
+ /* property definitions */
+ param_spec = g_param_spec_object ("session", "WockyJingleSession object",
+ "Jingle session object that owns this content.",
+ WOCKY_TYPE_JINGLE_SESSION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SESSION, param_spec);
+
+ param_spec = g_param_spec_string ("name", "Content name",
+ "A unique content name in the session.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_NAME, param_spec);
+
+ param_spec = g_param_spec_string ("content-ns", "Content namespace",
+ "Namespace identifying the content type.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONTENT_NS, param_spec);
+
+ param_spec = g_param_spec_string ("transport-ns", "Transport namespace",
+ "Namespace identifying the transport type.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TRANSPORT_NS, param_spec);
+
+ param_spec = g_param_spec_uint ("senders", "Stream senders",
+ "Valid senders for the stream.",
+ 0, G_MAXUINT32, WOCKY_JINGLE_CONTENT_SENDERS_NONE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SENDERS, param_spec);
+
+ param_spec = g_param_spec_uint ("state", "Content state",
+ "The current state that the content is in.",
+ 0, G_MAXUINT32, WOCKY_JINGLE_CONTENT_STATE_EMPTY,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STATE, param_spec);
+
+ param_spec = g_param_spec_string ("disposition", "Content disposition",
+ "Distinguishes between 'session' and other contents.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DISPOSITION, param_spec);
+
+ param_spec = g_param_spec_boolean ("locally-created", "Locally created",
+ "True if the content was created by the local client.",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_LOCALLY_CREATED, param_spec);
+
+ /* signal definitions */
+
+ signals[READY] = g_signal_new ("ready",
+ G_OBJECT_CLASS_TYPE (cls),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /**
+ * WockyJingleContent::new-candidates:
+ * @content: the content
+ * @candidates: (type GList) (element-type WockyJingleCandidate): a #GList of new candidates
+ *
+ * Emitted when new candidates are received from the peer.
+ */
+ signals[NEW_CANDIDATES] = g_signal_new (
+ "new-candidates",
+ G_TYPE_FROM_CLASS (cls),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ signals[NEW_SHARE_CHANNEL] = g_signal_new (
+ "new-share-channel",
+ G_TYPE_FROM_CLASS (cls),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ _wocky_signals_marshal_VOID__STRING_UINT,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_STRING, G_TYPE_UINT);
+
+ signals[COMPLETED] = g_signal_new (
+ "completed",
+ G_TYPE_FROM_CLASS (cls),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ /* This signal serves as notification that the WockyJingleContent is now
+ * meaningless; everything holding a reference should drop it after receiving
+ * 'removed'.
+ */
+ signals[REMOVED] = g_signal_new ("removed",
+ G_OBJECT_CLASS_TYPE (cls),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+
+static WockyJingleContentSenders
+get_default_senders (WockyJingleContent *c)
+{
+ WockyJingleContentSenders (*virtual_method)(WockyJingleContent *) = \
+ WOCKY_JINGLE_CONTENT_GET_CLASS (c)->get_default_senders;
+
+ g_assert (virtual_method != NULL);
+ return virtual_method (c);
+}
+
+
+static WockyJingleContentSenders
+parse_senders (const gchar *txt)
+{
+ if (txt == NULL)
+ return WOCKY_JINGLE_CONTENT_SENDERS_NONE;
+
+ if (!wocky_strdiff (txt, "initiator"))
+ return WOCKY_JINGLE_CONTENT_SENDERS_INITIATOR;
+ else if (!wocky_strdiff (txt, "responder"))
+ return WOCKY_JINGLE_CONTENT_SENDERS_RESPONDER;
+ else if (!wocky_strdiff (txt, "both"))
+ return WOCKY_JINGLE_CONTENT_SENDERS_BOTH;
+
+ return WOCKY_JINGLE_CONTENT_SENDERS_NONE;
+}
+
+static const gchar *
+produce_senders (WockyJingleContentSenders senders)
+{
+ switch (senders) {
+ case WOCKY_JINGLE_CONTENT_SENDERS_INITIATOR:
+ return "initiator";
+ case WOCKY_JINGLE_CONTENT_SENDERS_RESPONDER:
+ return "responder";
+ case WOCKY_JINGLE_CONTENT_SENDERS_BOTH:
+ return "both";
+ default:
+ DEBUG ("invalid content senders %u", senders);
+ g_assert_not_reached ();
+ }
+
+ /* to make gcc not complain */
+ return NULL;
+}
+
+
+#define SET_BAD_REQ(txt) \
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST, txt)
+
+static void
+new_transport_candidates_cb (WockyJingleTransportIface *trans,
+ GList *candidates, WockyJingleContent *content)
+{
+ /* just pass the signal on */
+ g_signal_emit (content, signals[NEW_CANDIDATES], 0, candidates);
+}
+
+static void
+transport_created (WockyJingleContent *c)
+{
+ void (*virtual_method)(WockyJingleContent *, WockyJingleTransportIface *) = \
+ WOCKY_JINGLE_CONTENT_GET_CLASS (c)->transport_created;
+
+ if (virtual_method != NULL)
+ virtual_method (c, c->priv->transport);
+}
+
+
+static void
+parse_description (WockyJingleContent *c, WockyNode *desc_node,
+ GError **error)
+{
+ void (*virtual_method)(WockyJingleContent *, WockyNode *,
+ GError **) = WOCKY_JINGLE_CONTENT_GET_CLASS (c)->parse_description;
+
+ g_assert (virtual_method != NULL);
+ virtual_method (c, desc_node, error);
+}
+
+static gboolean
+send_gtalk4_transport_accept (gpointer user_data)
+{
+ WockyJingleContent *c = WOCKY_JINGLE_CONTENT (user_data);
+ WockyJingleContentPrivate *priv = c->priv;
+ WockyNode *sess_node;
+ WockyStanza *msg = wocky_jingle_session_new_message (c->session,
+ WOCKY_JINGLE_ACTION_TRANSPORT_ACCEPT, &sess_node);
+
+ DEBUG ("Sending Gtalk4 'transport-accept' message to peer");
+ wocky_node_add_child_ns (sess_node, "transport", priv->transport_ns);
+
+ wocky_jingle_session_send (c->session, msg);
+
+ return FALSE;
+}
+
+void
+wocky_jingle_content_parse_add (WockyJingleContent *c,
+ WockyNode *content_node, gboolean google_mode, GError **error)
+{
+ WockyJingleContentPrivate *priv = c->priv;
+ const gchar *name, *creator, *senders, *disposition;
+ WockyNode *trans_node, *desc_node;
+ GType transport_type = 0;
+ WockyJingleTransportIface *trans = NULL;
+ WockyJingleDialect dialect = wocky_jingle_session_get_dialect (c->session);
+
+ priv->created_by_us = FALSE;
+
+ desc_node = wocky_node_get_child (content_node, "description");
+ trans_node = wocky_node_get_child (content_node, "transport");
+ creator = wocky_node_get_attribute (content_node, "creator");
+ name = wocky_node_get_attribute (content_node, "name");
+ senders = wocky_node_get_attribute (content_node, "senders");
+
+ g_assert (priv->transport_ns == NULL);
+
+ if (google_mode)
+ {
+ if (creator == NULL)
+ creator = "initiator";
+
+ /* the google protocols don't give the contents names, so put in a dummy
+ * value if none was set by the session*/
+ if (priv->name == NULL)
+ name = priv->name = g_strdup ("gtalk");
+ else
+ name = priv->name;
+
+ if (trans_node == NULL)
+ {
+ /* gtalk lj0.3 assumes google-p2p transport */
+ DEBUG ("detected GTalk3 dialect");
+
+ dialect = WOCKY_JINGLE_DIALECT_GTALK3;
+ g_object_set (c->session, "dialect", WOCKY_JINGLE_DIALECT_GTALK3, NULL);
+ transport_type = wocky_jingle_factory_lookup_transport (
+ wocky_jingle_session_get_factory (c->session),
+ "");
+
+ /* in practice we do support gtalk-p2p, so this can't happen */
+ if (G_UNLIKELY (transport_type == 0))
+ {
+ SET_BAD_REQ ("gtalk-p2p transport unsupported");
+ return;
+ }
+
+ priv->transport_ns = g_strdup ("");
+ }
+ }
+ else
+ {
+ if (creator == NULL &&
+ wocky_jingle_session_peer_has_cap (c->session,
+ WOCKY_QUIRK_GOOGLE_WEBMAIL_CLIENT))
+ {
+ if (wocky_jingle_content_creator_is_initiator (c))
+ creator = "initiator";
+ else
+ creator = "responder";
+
+ DEBUG ("Working around GMail omitting creator=''; assuming '%s'",
+ creator);
+ }
+
+ if ((trans_node == NULL) || (creator == NULL) || (name == NULL))
+ {
+ SET_BAD_REQ ("missing required content attributes or elements");
+ return;
+ }
+
+ /* In proper protocols the name comes from the stanza */
+ g_assert (priv->name == NULL);
+ priv->name = g_strdup (name);
+ }
+
+ /* if we didn't set it to google-p2p implicitly already, detect it */
+ if (transport_type == 0)
+ {
+ const gchar *ns = wocky_node_get_ns (trans_node);
+
+ transport_type = wocky_jingle_factory_lookup_transport (
+ wocky_jingle_session_get_factory (c->session), ns);
+
+ if (transport_type == 0)
+ {
+ SET_BAD_REQ ("unsupported content transport");
+ return;
+ }
+
+ priv->transport_ns = g_strdup (ns);
+ }
+
+ if (senders == NULL)
+ priv->senders = get_default_senders (c);
+ else
+ priv->senders = parse_senders (senders);
+
+ if (priv->senders == WOCKY_JINGLE_CONTENT_SENDERS_NONE)
+ {
+ SET_BAD_REQ ("invalid content senders");
+ return;
+ }
+
+ parse_description (c, desc_node, error);
+ if (*error != NULL)
+ return;
+
+ disposition = wocky_node_get_attribute (content_node, "disposition");
+ if (disposition == NULL)
+ disposition = "session";
+
+ if (wocky_strdiff (disposition, priv->disposition))
+ {
+ g_free (priv->disposition);
+ priv->disposition = g_strdup (disposition);
+ }
+
+ DEBUG ("content creating new transport type %s", g_type_name (transport_type));
+
+ trans = wocky_jingle_transport_iface_new (transport_type,
+ c, priv->transport_ns);
+
+ g_signal_connect (trans, "new-candidates",
+ (GCallback) new_transport_candidates_cb, c);
+
+ /* Depending on transport, there may be initial candidates specified here */
+ if (trans_node != NULL)
+ {
+ wocky_jingle_transport_iface_parse_candidates (trans, trans_node, error);
+ if (*error)
+ {
+ g_object_unref (trans);
+ return;
+ }
+ }
+
+ g_assert (priv->transport == NULL);
+ priv->transport = trans;
+ transport_created (c);
+
+ g_assert (priv->creator == NULL);
+ priv->creator = g_strdup (creator);
+
+ priv->state = WOCKY_JINGLE_CONTENT_STATE_NEW;
+
+ /* GTalk4 seems to require "transport-accept" for acknowledging
+ * the transport type. wjt confirms that this is apparently necessary for
+ * incoming calls to work.
+ */
+ if (dialect == WOCKY_JINGLE_DIALECT_GTALK4)
+ priv->gtalk4_event_id = g_idle_add (send_gtalk4_transport_accept, c);
+
+ return;
+}
+
+static guint
+new_share_channel (WockyJingleContent *c, const gchar *name)
+{
+ WockyJingleContentPrivate *priv = c->priv;
+ WockyJingleTransportGoogle *gtrans = NULL;
+
+ if (priv->transport &&
+ WOCKY_IS_JINGLE_TRANSPORT_GOOGLE (priv->transport))
+ {
+ guint id = priv->last_share_channel_component_id + 1;
+
+ gtrans = WOCKY_JINGLE_TRANSPORT_GOOGLE (priv->transport);
+
+ if (!jingle_transport_google_set_component_name (gtrans, name, id))
+ return 0;
+
+ priv->last_share_channel_component_id++;
+
+ DEBUG ("New Share channel '%s' with id : %d", name, id);
+
+ g_signal_emit (c, signals[NEW_SHARE_CHANNEL], 0, name, id);
+
+ return priv->last_share_channel_component_id;
+ }
+ return 0;
+}
+
+guint
+wocky_jingle_content_create_share_channel (WockyJingleContent *self,
+ const gchar *name)
+{
+ WockyJingleContentPrivate *priv = self->priv;
+ WockyNode *sess_node, *channel_node;
+ WockyStanza *msg = NULL;
+
+ /* Send the info action before creating the channel, in case candidates need
+ to be sent on the signal emit. It doesn't matter if the channel already
+ exists anyways... */
+ msg = wocky_jingle_session_new_message (self->session,
+ WOCKY_JINGLE_ACTION_INFO, &sess_node);
+
+ DEBUG ("Sending 'info' message to peer : channel %s", name);
+ channel_node = wocky_node_add_child_ns (sess_node, "channel",
+ priv->content_ns);
+ wocky_node_set_attribute (channel_node, "name", name);
+
+ wocky_jingle_session_send (self->session, msg);
+
+ return new_share_channel (self, name);
+}
+
+void
+wocky_jingle_content_send_complete (WockyJingleContent *self)
+{
+ WockyJingleContentPrivate *priv = self->priv;
+ WockyNode *sess_node;
+ WockyStanza *msg = NULL;
+
+ msg = wocky_jingle_session_new_message (self->session,
+ WOCKY_JINGLE_ACTION_INFO, &sess_node);
+
+ DEBUG ("Sending 'info' message to peer : complete");
+ wocky_node_add_child_ns (sess_node, "complete", priv->content_ns);
+
+ wocky_jingle_session_send (self->session, msg);
+
+}
+
+void
+wocky_jingle_content_parse_info (WockyJingleContent *c,
+ WockyNode *content_node, GError **error)
+{
+ WockyNode *channel_node;
+ WockyNode *complete_node;
+
+ channel_node = wocky_node_get_child (content_node, "channel");
+ complete_node = wocky_node_get_child (content_node, "complete");
+
+ DEBUG ("parsing info message : %p - %p", channel_node, complete_node);
+ if (channel_node)
+ {
+ const gchar *name;
+ name = wocky_node_get_attribute (channel_node, "name");
+ if (name != NULL)
+ new_share_channel (c, name);
+ }
+ else if (complete_node)
+ {
+ g_signal_emit (c, signals[COMPLETED], 0);
+ }
+
+}
+
+void
+wocky_jingle_content_parse_accept (WockyJingleContent *c,
+ WockyNode *content_node, gboolean google_mode, GError **error)
+{
+ WockyJingleContentPrivate *priv = c->priv;
+ const gchar *senders;
+ WockyNode *trans_node, *desc_node;
+ WockyJingleDialect dialect = wocky_jingle_session_get_dialect (c->session);
+ WockyJingleContentSenders newsenders;
+
+ desc_node = wocky_node_get_child (content_node, "description");
+ trans_node = wocky_node_get_child (content_node, "transport");
+ senders = wocky_node_get_attribute (content_node, "senders");
+
+ if (WOCKY_IS_JINGLE_MEDIA_RTP (c) &&
+ WOCKY_JINGLE_DIALECT_IS_GOOGLE (dialect) && trans_node == NULL)
+ {
+ DEBUG ("no transport node, assuming GTalk3 dialect");
+ /* gtalk lj0.3 assumes google-p2p transport */
+ g_object_set (c->session, "dialect", WOCKY_JINGLE_DIALECT_GTALK3, NULL);
+ }
+
+ if (senders == NULL)
+ newsenders = get_default_senders (c);
+ else
+ newsenders = parse_senders (senders);
+
+ if (newsenders == WOCKY_JINGLE_CONTENT_SENDERS_NONE)
+ {
+ SET_BAD_REQ ("invalid content senders");
+ return;
+ }
+
+ if (newsenders != priv->senders)
+ {
+ DEBUG ("changing senders from %s to %s", produce_senders (priv->senders),
+ produce_senders (newsenders));
+ priv->senders = newsenders;
+ g_object_notify ((GObject *) c, "senders");
+ }
+
+ parse_description (c, desc_node, error);
+ if (*error != NULL)
+ return;
+
+ priv->state = WOCKY_JINGLE_CONTENT_STATE_ACKNOWLEDGED;
+ g_object_notify ((GObject *) c, "state");
+
+ if (trans_node != NULL)
+ {
+ wocky_jingle_transport_iface_parse_candidates (priv->transport,
+ trans_node, NULL);
+ }
+}
+
+void
+wocky_jingle_content_parse_description_info (WockyJingleContent *c,
+ WockyNode *content_node, GError **error)
+{
+ WockyJingleContentPrivate *priv = c->priv;
+ WockyNode *desc_node;
+ desc_node = wocky_node_get_child (content_node, "description");
+ if (desc_node == NULL)
+ {
+ SET_BAD_REQ ("invalid description-info action");
+ return;
+ }
+
+ if (priv->created_by_us && priv->state < WOCKY_JINGLE_CONTENT_STATE_ACKNOWLEDGED)
+ {
+ /* The stream was created by us and the other side didn't acknowledge it
+ * yet, thus we don't have their codec information, thus the
+ * description-info isn't meaningful and can be ignored */
+ DEBUG ("Ignoring description-info as we didn't receive the codecs yet");
+ return;
+ }
+
+ parse_description (c, desc_node, error);
+}
+
+
+void
+wocky_jingle_content_produce_node (WockyJingleContent *c,
+ WockyNode *parent,
+ gboolean include_description,
+ gboolean include_transport,
+ WockyNode **trans_node_out)
+{
+ WockyJingleContentPrivate *priv = c->priv;
+ WockyNode *content_node, *trans_node;
+ WockyJingleDialect dialect = wocky_jingle_session_get_dialect (c->session);
+ void (*produce_desc)(WockyJingleContent *, WockyNode *) =
+ WOCKY_JINGLE_CONTENT_GET_CLASS (c)->produce_description;
+
+ if ((dialect == WOCKY_JINGLE_DIALECT_GTALK3) ||
+ (dialect == WOCKY_JINGLE_DIALECT_GTALK4))
+ {
+ content_node = parent;
+ }
+ else
+ {
+ content_node = wocky_node_add_child (parent, "content");
+ wocky_node_set_attributes (content_node,
+ "name", priv->name,
+ "senders", produce_senders (priv->senders),
+ NULL);
+
+ if (wocky_jingle_content_creator_is_initiator (c))
+ wocky_node_set_attribute (content_node, "creator", "initiator");
+ else
+ wocky_node_set_attribute (content_node, "creator", "responder");
+ }
+
+ if (include_description)
+ produce_desc (c, content_node);
+
+ if (include_transport)
+ {
+ if (dialect == WOCKY_JINGLE_DIALECT_GTALK3)
+ {
+ /* GTalk 03 doesn't use a transport, but assumes gtalk-p2p */
+ trans_node = parent;
+ }
+ else
+ {
+ trans_node = wocky_node_add_child_ns (content_node, "transport",
+ priv->transport_ns);
+ }
+
+ if (trans_node_out != NULL)
+ *trans_node_out = trans_node;
+ }
+}
+
+void
+wocky_jingle_content_update_senders (WockyJingleContent *c,
+ WockyNode *content_node, GError **error)
+{
+ WockyJingleContentPrivate *priv = c->priv;
+ WockyJingleContentSenders senders;
+
+ senders = parse_senders (wocky_node_get_attribute (content_node, "senders"));
+
+ if (senders == WOCKY_JINGLE_CONTENT_SENDERS_NONE)
+ {
+ SET_BAD_REQ ("invalid content senders in stream");
+ return;
+ }
+
+ priv->senders = senders;
+ g_object_notify ((GObject *) c, "senders");
+}
+
+void
+wocky_jingle_content_parse_transport_info (WockyJingleContent *self,
+ WockyNode *trans_node, GError **error)
+{
+ WockyJingleContentPrivate *priv = self->priv;
+
+ wocky_jingle_transport_iface_parse_candidates (priv->transport, trans_node, error);
+}
+
+
+/**
+ * wocky_jingle_content_add_candidates:
+ * @self: the content
+ * @li: (element-type WockyJingleCandidate) (transfer full): a list of
+ * #WockyJingleCandidate structs, allocated with wocky_jingle_candidate_new().
+ *
+ * Adds the candidates listed in @li to the content, communicating them to the
+ * peer if appropriate.
+ */
+void
+wocky_jingle_content_add_candidates (WockyJingleContent *self, GList *li)
+{
+ WockyJingleContentPrivate *priv = self->priv;
+
+ DEBUG ("called content: %s created_by_us: %d", priv->name,
+ priv->created_by_us);
+
+ if (li == NULL)
+ return;
+
+ wocky_jingle_transport_iface_new_local_candidates (priv->transport, li);
+
+ if (!priv->have_local_candidates)
+ {
+ priv->have_local_candidates = TRUE;
+ /* Maybe we were waiting for at least one candidate? */
+ _maybe_ready (self);
+ }
+
+ /* If the content exists on the wire, let the transport send this candidate
+ * if it wants to.
+ */
+ if (priv->state > WOCKY_JINGLE_CONTENT_STATE_EMPTY)
+ wocky_jingle_transport_iface_send_candidates (priv->transport, FALSE);
+}
+
+/* Returns whether the content is ready to be signalled (initiated, for local
+ * streams, or acknowledged, for remote streams. */
+gboolean
+wocky_jingle_content_is_ready (WockyJingleContent *self)
+{
+ WockyJingleContentPrivate *priv = self->priv;
+
+ if (priv->created_by_us)
+ {
+ /* If it's created by us, media ready, not signalled, and we have
+ * at least one local candidate, it's ready to be added. */
+ if (priv->media_ready && priv->state == WOCKY_JINGLE_CONTENT_STATE_EMPTY &&
+ (!WOCKY_IS_JINGLE_MEDIA_RTP (self) || priv->have_local_candidates))
+ return TRUE;
+ }
+ else
+ {
+ /* If it's created by peer, media and transports ready,
+ * and not acknowledged yet, it's ready for acceptance. */
+ if (priv->media_ready && priv->state == WOCKY_JINGLE_CONTENT_STATE_NEW &&
+ (!WOCKY_IS_JINGLE_MEDIA_RTP (self) ||
+ wocky_jingle_transport_iface_can_accept (priv->transport)))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+send_content_add_or_accept (WockyJingleContent *self)
+{
+ WockyJingleContentPrivate *priv = self->priv;
+ WockyStanza *msg;
+ WockyNode *sess_node, *transport_node;
+ WockyJingleAction action;
+ WockyJingleContentState new_state = WOCKY_JINGLE_CONTENT_STATE_EMPTY;
+
+ g_assert (wocky_jingle_content_is_ready (self));
+
+ if (priv->created_by_us)
+ {
+ /* TODO: set a timer for acknowledgement */
+ action = WOCKY_JINGLE_ACTION_CONTENT_ADD;
+ new_state = WOCKY_JINGLE_CONTENT_STATE_SENT;
+ }
+ else
+ {
+ action = WOCKY_JINGLE_ACTION_CONTENT_ACCEPT;
+ new_state = WOCKY_JINGLE_CONTENT_STATE_ACKNOWLEDGED;
+ }
+
+ msg = wocky_jingle_session_new_message (self->session,
+ action, &sess_node);
+ wocky_jingle_content_produce_node (self, sess_node, TRUE, TRUE,
+ &transport_node);
+ wocky_jingle_transport_iface_inject_candidates (priv->transport,
+ transport_node);
+ wocky_jingle_session_send (self->session, msg);
+
+ priv->state = new_state;
+ g_object_notify (G_OBJECT (self), "state");
+}
+
+static void
+_maybe_ready (WockyJingleContent *self)
+{
+ WockyJingleContentPrivate *priv = self->priv;
+ WockyJingleState state;
+
+ if (!wocky_jingle_content_is_ready (self))
+ return;
+
+ /* If content disposition is session and session
+ * is not yet acknowledged/active, we signall
+ * the readiness to the session and let it take
+ * care of it. Otherwise, we can deal with it
+ * ourselves. */
+
+ g_object_get (self->session, "state", &state, NULL);
+
+ if (!wocky_strdiff (priv->disposition, "session") &&
+ (state < WOCKY_JINGLE_STATE_PENDING_ACCEPT_SENT))
+ {
+ /* Notify the session that we're ready for
+ * session-initiate/session-accept */
+ g_signal_emit (self, signals[READY], 0);
+ }
+ else
+ {
+ if (state >= WOCKY_JINGLE_STATE_PENDING_INITIATE_SENT)
+ {
+ send_content_add_or_accept (self);
+
+ /* if neccessary, transmit the candidates */
+ wocky_jingle_transport_iface_send_candidates (priv->transport,
+ FALSE);
+ }
+ else
+ {
+ /* non session-disposition content ready without session
+ * being initiated at all? */
+ DEBUG ("session not initiated yet, ignoring non-session ready content");
+ return;
+ }
+ }
+}
+
+void
+wocky_jingle_content_maybe_send_description (WockyJingleContent *self)
+{
+ WockyJingleContentPrivate *priv = self->priv;
+
+ /* If we didn't send the content yet there is no reason to send a
+ * description-info to update it */
+ if (priv->state < WOCKY_JINGLE_CONTENT_STATE_SENT)
+ return;
+
+ if (wocky_jingle_session_defines_action (self->session,
+ WOCKY_JINGLE_ACTION_DESCRIPTION_INFO))
+ {
+ WockyNode *sess_node;
+ WockyStanza *msg = wocky_jingle_session_new_message (self->session,
+ WOCKY_JINGLE_ACTION_DESCRIPTION_INFO, &sess_node);
+
+ wocky_jingle_content_produce_node (self, sess_node, TRUE, FALSE, NULL);
+ wocky_jingle_session_send (self->session, msg);
+ }
+ else
+ {
+ DEBUG ("not sending description-info, speaking an old dialect");
+ }
+}
+
+
+/* Used when session-initiate is sent (so all initial contents transmit their
+ * candidates), and when we detect gtalk3 after we've transmitted some
+ * candidates. */
+void
+wocky_jingle_content_retransmit_candidates (WockyJingleContent *self,
+ gboolean all)
+{
+ wocky_jingle_transport_iface_send_candidates (self->priv->transport, all);
+}
+
+void
+wocky_jingle_content_inject_candidates (WockyJingleContent *self,
+ WockyNode *transport_node)
+{
+ wocky_jingle_transport_iface_inject_candidates (self->priv->transport,
+ transport_node);
+}
+
+
+/* Called by a subclass when the media is ready (e.g. we got local codecs) */
+void
+_wocky_jingle_content_set_media_ready (WockyJingleContent *self)
+{
+ WockyJingleContentPrivate *priv = self->priv;
+
+ DEBUG ("media ready on content: %s created_by_us: %d", priv->name,
+ priv->created_by_us);
+
+ priv->media_ready = TRUE;
+
+ _maybe_ready (self);
+}
+
+void
+wocky_jingle_content_set_transport_state (WockyJingleContent *self,
+ WockyJingleTransportState state)
+{
+ WockyJingleContentPrivate *priv = self->priv;
+
+ g_object_set (priv->transport, "state", state, NULL);
+
+ _maybe_ready (self);
+}
+
+GList *
+wocky_jingle_content_get_remote_candidates (WockyJingleContent *c)
+{
+ WockyJingleContentPrivate *priv = c->priv;
+
+ return wocky_jingle_transport_iface_get_remote_candidates (priv->transport);
+}
+
+GList *
+wocky_jingle_content_get_local_candidates (WockyJingleContent *c)
+{
+ WockyJingleContentPrivate *priv = c->priv;
+
+ return wocky_jingle_transport_iface_get_local_candidates (priv->transport);
+}
+
+gboolean
+wocky_jingle_content_get_credentials (WockyJingleContent *c,
+ gchar **ufrag, gchar **pwd)
+{
+ WockyJingleContentPrivate *priv = c->priv;
+
+ return jingle_transport_get_credentials (priv->transport, ufrag, pwd);
+}
+
+gboolean
+wocky_jingle_content_change_direction (WockyJingleContent *c,
+ WockyJingleContentSenders senders)
+{
+ WockyJingleContentPrivate *priv = c->priv;
+ WockyStanza *msg;
+ WockyNode *sess_node;
+ WockyJingleDialect dialect = wocky_jingle_session_get_dialect (c->session);
+
+ if (senders == priv->senders)
+ return TRUE;
+
+ priv->senders = senders;
+ g_object_notify (G_OBJECT (c), "senders");
+
+ if (WOCKY_JINGLE_DIALECT_IS_GOOGLE (dialect))
+ {
+ DEBUG ("ignoring direction change request for GTalk stream");
+ return FALSE;
+ }
+
+ if (priv->state >= WOCKY_JINGLE_CONTENT_STATE_SENT)
+ {
+ msg = wocky_jingle_session_new_message (c->session,
+ WOCKY_JINGLE_ACTION_CONTENT_MODIFY, &sess_node);
+ wocky_jingle_content_produce_node (c, sess_node, FALSE, FALSE, NULL);
+ wocky_jingle_session_send (c->session, msg);
+ }
+
+ /* FIXME: actually check whether remote end accepts our content-modify */
+ return TRUE;
+}
+
+static void
+_on_remove_reply (
+ GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyJingleContent *c = WOCKY_JINGLE_CONTENT (user_data);
+ WockyJingleContentPrivate *priv = c->priv;
+
+ g_assert (priv->state == WOCKY_JINGLE_CONTENT_STATE_REMOVING);
+
+ DEBUG ("%p", c);
+
+ /* Everything holding a reference to a content should drop it after receiving
+ * 'removed'.
+ */
+ g_signal_emit (c, signals[REMOVED], 0);
+ g_object_unref (c);
+}
+
+static void
+_content_remove (WockyJingleContent *c,
+ gboolean signal_peer,
+ WockyJingleReason reason)
+{
+ WockyJingleContentPrivate *priv = c->priv;
+ WockyStanza *msg;
+ WockyNode *sess_node;
+
+ DEBUG ("called for %p (%s)", c, priv->name);
+
+ /* If we were already signalled and removal is not a side-effect of
+ * something else (sesssion termination, or removal by peer),
+ * we have to signal removal to the peer. */
+ if (signal_peer && (priv->state != WOCKY_JINGLE_CONTENT_STATE_EMPTY))
+ {
+ if (priv->state == WOCKY_JINGLE_CONTENT_STATE_REMOVING)
+ {
+ DEBUG ("ignoring request to remove content which is already being removed");
+ return;
+ }
+
+ priv->state = WOCKY_JINGLE_CONTENT_STATE_REMOVING;
+ g_object_notify ((GObject *) c, "state");
+
+ msg = wocky_jingle_session_new_message (c->session,
+ reason == WOCKY_JINGLE_REASON_UNKNOWN ?
+ WOCKY_JINGLE_ACTION_CONTENT_REMOVE : WOCKY_JINGLE_ACTION_CONTENT_REJECT,
+ &sess_node);
+
+ if (reason != WOCKY_JINGLE_REASON_UNKNOWN)
+ {
+ WockyNode *reason_node = wocky_node_add_child_with_content (sess_node,
+ "reason", NULL);
+ wocky_node_add_child_with_content (reason_node,
+ wocky_jingle_session_get_reason_name (reason), NULL);
+ }
+
+ wocky_jingle_content_produce_node (c, sess_node, FALSE, FALSE, NULL);
+ wocky_porter_send_iq_async (wocky_jingle_session_get_porter (c->session),
+ msg, NULL, _on_remove_reply, g_object_ref (c));
+ g_object_unref (msg);
+ }
+ else
+ {
+ DEBUG ("signalling removed with %u refs", G_OBJECT (c)->ref_count);
+ /* Everything holding a reference to a content should drop it after receiving
+ * 'removed'.
+ */
+ g_signal_emit (c, signals[REMOVED], 0);
+ }
+}
+
+void
+wocky_jingle_content_remove (WockyJingleContent *c,
+ gboolean signal_peer)
+{
+ _content_remove (c, signal_peer, WOCKY_JINGLE_REASON_UNKNOWN);
+}
+
+void
+wocky_jingle_content_reject (WockyJingleContent *c,
+ WockyJingleReason reason)
+{
+ _content_remove (c, TRUE, reason);
+}
+
+gboolean
+wocky_jingle_content_is_created_by_us (WockyJingleContent *c)
+{
+ return c->priv->created_by_us;
+}
+
+gboolean
+wocky_jingle_content_creator_is_initiator (WockyJingleContent *c)
+{
+ gboolean session_created_by_us;
+
+ g_object_get (c->session, "local-initiator", &session_created_by_us, NULL);
+
+ return (c->priv->created_by_us == session_created_by_us);
+}
+
+const gchar *
+wocky_jingle_content_get_name (WockyJingleContent *self)
+{
+ return self->priv->name;
+}
+
+const gchar *
+wocky_jingle_content_get_ns (WockyJingleContent *self)
+{
+ return self->priv->content_ns;
+}
+
+const gchar *
+wocky_jingle_content_get_transport_ns (WockyJingleContent *self)
+{
+ return self->priv->transport_ns;
+}
+
+const gchar *
+wocky_jingle_content_get_disposition (WockyJingleContent *self)
+{
+ return self->priv->disposition;
+}
+
+WockyJingleTransportType
+wocky_jingle_content_get_transport_type (WockyJingleContent *c)
+{
+ return wocky_jingle_transport_iface_get_transport_type (c->priv->transport);
+}
+
+static gboolean
+jingle_content_has_direction (WockyJingleContent *self,
+ gboolean sending)
+{
+ WockyJingleContentPrivate *priv = self->priv;
+ gboolean initiated_by_us;
+
+ g_object_get (self->session, "local-initiator",
+ &initiated_by_us, NULL);
+
+ switch (priv->senders)
+ {
+ case WOCKY_JINGLE_CONTENT_SENDERS_BOTH:
+ return TRUE;
+ case WOCKY_JINGLE_CONTENT_SENDERS_NONE:
+ return FALSE;
+ case WOCKY_JINGLE_CONTENT_SENDERS_INITIATOR:
+ return sending ? initiated_by_us : !initiated_by_us;
+ case WOCKY_JINGLE_CONTENT_SENDERS_RESPONDER:
+ return sending ? !initiated_by_us : initiated_by_us;
+ }
+
+ return FALSE;
+}
+
+gboolean
+wocky_jingle_content_sending (WockyJingleContent *self)
+{
+ return jingle_content_has_direction (self, TRUE);
+}
+
+gboolean
+wocky_jingle_content_receiving (WockyJingleContent *self)
+{
+ return jingle_content_has_direction (self, FALSE);
+}
+
+void
+wocky_jingle_content_set_sending (WockyJingleContent *self,
+ gboolean send)
+{
+ WockyJingleContentPrivate *priv = self->priv;
+ WockyJingleContentSenders senders;
+ gboolean initiated_by_us;
+
+ if (send == wocky_jingle_content_sending (self))
+ return;
+
+ g_object_get (self->session, "local-initiator",
+ &initiated_by_us, NULL);
+
+ if (send)
+ {
+ if (priv->senders == WOCKY_JINGLE_CONTENT_SENDERS_NONE)
+ senders = (initiated_by_us ? WOCKY_JINGLE_CONTENT_SENDERS_INITIATOR :
+ WOCKY_JINGLE_CONTENT_SENDERS_RESPONDER);
+ else
+ senders = WOCKY_JINGLE_CONTENT_SENDERS_BOTH;
+ }
+ else
+ {
+ if (priv->senders == WOCKY_JINGLE_CONTENT_SENDERS_BOTH)
+ senders = (initiated_by_us ? WOCKY_JINGLE_CONTENT_SENDERS_RESPONDER :
+ WOCKY_JINGLE_CONTENT_SENDERS_INITIATOR);
+ else
+ senders = WOCKY_JINGLE_CONTENT_SENDERS_NONE;
+ }
+
+ if (senders == WOCKY_JINGLE_CONTENT_SENDERS_NONE)
+ wocky_jingle_content_remove (self, TRUE);
+ else
+ wocky_jingle_content_change_direction (self, senders);
+}
+
+
+void
+wocky_jingle_content_request_receiving (WockyJingleContent *self,
+ gboolean receive)
+{
+ WockyJingleContentPrivate *priv = self->priv;
+ WockyJingleContentSenders senders;
+ gboolean initiated_by_us;
+
+ if (receive == wocky_jingle_content_receiving (self))
+ return;
+
+ g_object_get (self->session, "local-initiator",
+ &initiated_by_us, NULL);
+
+ if (receive)
+ {
+ if (priv->senders == WOCKY_JINGLE_CONTENT_SENDERS_NONE)
+ senders = (initiated_by_us ? WOCKY_JINGLE_CONTENT_SENDERS_RESPONDER :
+ WOCKY_JINGLE_CONTENT_SENDERS_INITIATOR);
+ else
+ senders = WOCKY_JINGLE_CONTENT_SENDERS_BOTH;
+ }
+ else
+ {
+ if (priv->senders == WOCKY_JINGLE_CONTENT_SENDERS_BOTH)
+ senders = (initiated_by_us ? WOCKY_JINGLE_CONTENT_SENDERS_INITIATOR :
+ WOCKY_JINGLE_CONTENT_SENDERS_RESPONDER);
+ else
+ senders = WOCKY_JINGLE_CONTENT_SENDERS_NONE;
+ }
+
+
+ if (senders == WOCKY_JINGLE_CONTENT_SENDERS_NONE)
+ wocky_jingle_content_remove (self, TRUE);
+ else
+ wocky_jingle_content_change_direction (self, senders);
+}
diff --git a/wocky/wocky-jingle-content.h b/wocky/wocky-jingle-content.h
new file mode 100644
index 0000000..e161743
--- /dev/null
+++ b/wocky/wocky-jingle-content.h
@@ -0,0 +1,168 @@
+/*
+ * wocky-jingle-content.h - Header for WockyJingleContent
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#if !defined (WOCKY_H_INSIDE) && !defined (WOCKY_COMPILATION)
+# error "Only <wocky/wocky.h> can be included directly."
+#endif
+
+#ifndef __JINGLE_CONTENT_H__
+#define __JINGLE_CONTENT_H__
+
+#include <glib-object.h>
+
+#include "wocky-jingle-factory.h"
+#include "wocky-jingle-transport-iface.h"
+#include "wocky-jingle-types.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+ WOCKY_JINGLE_MEDIA_TYPE_NONE = 0,
+ WOCKY_JINGLE_MEDIA_TYPE_AUDIO,
+ WOCKY_JINGLE_MEDIA_TYPE_VIDEO,
+} WockyJingleMediaType;
+
+typedef enum {
+ WOCKY_JINGLE_CONTENT_STATE_EMPTY = 0,
+ WOCKY_JINGLE_CONTENT_STATE_NEW,
+ WOCKY_JINGLE_CONTENT_STATE_SENT,
+ WOCKY_JINGLE_CONTENT_STATE_ACKNOWLEDGED,
+ WOCKY_JINGLE_CONTENT_STATE_REMOVING
+} WockyJingleContentState;
+
+struct _WockyJingleCandidate {
+ WockyJingleTransportProtocol protocol;
+ WockyJingleCandidateType type;
+
+ gchar *id;
+ gchar *address;
+ int port;
+ int component;
+ int generation;
+
+ int preference;
+ gchar *username;
+ gchar *password;
+ int network;
+};
+
+typedef struct _WockyJingleContentClass WockyJingleContentClass;
+
+GType wocky_jingle_content_get_type (void);
+
+/* TYPE MACROS */
+#define WOCKY_TYPE_JINGLE_CONTENT \
+ (wocky_jingle_content_get_type ())
+#define WOCKY_JINGLE_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), WOCKY_TYPE_JINGLE_CONTENT, \
+ WockyJingleContent))
+#define WOCKY_JINGLE_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), WOCKY_TYPE_JINGLE_CONTENT, \
+ WockyJingleContentClass))
+#define WOCKY_IS_JINGLE_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), WOCKY_TYPE_JINGLE_CONTENT))
+#define WOCKY_IS_JINGLE_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), WOCKY_TYPE_JINGLE_CONTENT))
+#define WOCKY_JINGLE_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), WOCKY_TYPE_JINGLE_CONTENT, \
+ WockyJingleContentClass))
+
+struct _WockyJingleContentClass {
+ GObjectClass parent_class;
+
+ void (*parse_description) (WockyJingleContent *, WockyNode *,
+ GError **);
+ void (*produce_description) (WockyJingleContent *, WockyNode *);
+ void (*transport_created) (WockyJingleContent *,
+ WockyJingleTransportIface *);
+ WockyJingleContentSenders (*get_default_senders) (WockyJingleContent *);
+};
+
+typedef struct _WockyJingleContentPrivate WockyJingleContentPrivate;
+
+struct _WockyJingleContent {
+ GObject parent;
+ WockyJingleContentPrivate *priv;
+
+ WockyJingleSession *session;
+};
+
+void wocky_jingle_content_parse_add (WockyJingleContent *c,
+ WockyNode *content_node, gboolean google_mode, GError **error);
+void wocky_jingle_content_update_senders (WockyJingleContent *c,
+ WockyNode *content_node, GError **error);
+void wocky_jingle_content_produce_node (WockyJingleContent *c,
+ WockyNode *parent,
+ gboolean include_description,
+ gboolean include_transport,
+ WockyNode **trans_node_out);
+void wocky_jingle_content_parse_accept (WockyJingleContent *c,
+ WockyNode *content_node, gboolean google_mode, GError **error);
+
+void wocky_jingle_content_parse_info (WockyJingleContent *c,
+ WockyNode *content_node, GError **error);
+void wocky_jingle_content_parse_transport_info (WockyJingleContent *self,
+ WockyNode *trans_node, GError **error);
+void wocky_jingle_content_parse_description_info (WockyJingleContent *self,
+ WockyNode *trans_node, GError **error);
+guint wocky_jingle_content_create_share_channel (WockyJingleContent *self,
+ const gchar *name);
+void wocky_jingle_content_add_candidates (WockyJingleContent *self, GList *li);
+void _wocky_jingle_content_set_media_ready (WockyJingleContent *self);
+gboolean wocky_jingle_content_is_ready (WockyJingleContent *self);
+void wocky_jingle_content_set_transport_state (WockyJingleContent *content,
+ WockyJingleTransportState state);
+void wocky_jingle_content_remove (WockyJingleContent *c, gboolean signal_peer);
+void wocky_jingle_content_reject (WockyJingleContent *c,
+ WockyJingleReason reason);
+
+GList *wocky_jingle_content_get_remote_candidates (WockyJingleContent *c);
+GList *wocky_jingle_content_get_local_candidates (WockyJingleContent *c);
+gboolean wocky_jingle_content_get_credentials (WockyJingleContent *c,
+ gchar **ufrag, gchar **pwd);
+gboolean wocky_jingle_content_change_direction (WockyJingleContent *c,
+ WockyJingleContentSenders senders);
+void wocky_jingle_content_retransmit_candidates (WockyJingleContent *self,
+ gboolean all);
+void wocky_jingle_content_inject_candidates (WockyJingleContent *self,
+ WockyNode *transport_node);
+gboolean wocky_jingle_content_is_created_by_us (WockyJingleContent *c);
+gboolean wocky_jingle_content_creator_is_initiator (WockyJingleContent *c);
+
+const gchar *wocky_jingle_content_get_name (WockyJingleContent *self);
+const gchar *wocky_jingle_content_get_ns (WockyJingleContent *self);
+const gchar *wocky_jingle_content_get_disposition (WockyJingleContent *self);
+WockyJingleTransportType wocky_jingle_content_get_transport_type (WockyJingleContent *c);
+const gchar *wocky_jingle_content_get_transport_ns (WockyJingleContent *self);
+
+void wocky_jingle_content_maybe_send_description (WockyJingleContent *self);
+
+gboolean wocky_jingle_content_sending (WockyJingleContent *self);
+gboolean wocky_jingle_content_receiving (WockyJingleContent *self);
+
+void wocky_jingle_content_set_sending (WockyJingleContent *self,
+ gboolean send);
+void wocky_jingle_content_request_receiving (WockyJingleContent *self,
+ gboolean receive);
+
+void wocky_jingle_content_send_complete (WockyJingleContent *self);
+
+G_END_DECLS
+
+#endif /* __JINGLE_CONTENT_H__ */
+
diff --git a/wocky/wocky-jingle-factory.c b/wocky/wocky-jingle-factory.c
new file mode 100644
index 0000000..7d78ce6
--- /dev/null
+++ b/wocky/wocky-jingle-factory.c
@@ -0,0 +1,620 @@
+/*
+ * wocky-jingle-factory.c - Support for XEP-0166 (Jingle)
+ *
+ * Copyright (C) 2006-2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "wocky-jingle-factory.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <glib.h>
+
+#define WOCKY_DEBUG_FLAG WOCKY_DEBUG_JINGLE
+
+#include "wocky-debug-internal.h"
+#include "wocky-signals-marshal.h"
+#include "wocky-jingle-media-rtp.h"
+#include "wocky-jingle-session.h"
+#include "wocky-jingle-transport-google.h"
+#include "wocky-jingle-transport-rawudp.h"
+#include "wocky-jingle-transport-iceudp.h"
+#include "wocky-namespaces.h"
+#include "wocky-session.h"
+#include "wocky-utils.h"
+
+#include "wocky-google-relay.h"
+
+G_DEFINE_TYPE(WockyJingleFactory, wocky_jingle_factory, G_TYPE_OBJECT);
+
+/* signal enum */
+enum
+{
+ NEW_SESSION,
+ QUERY_CAP,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum
+{
+ PROP_SESSION = 1,
+ LAST_PROPERTY
+};
+
+struct _WockyJingleFactoryPrivate
+{
+ WockySession *session;
+ WockyPorter *porter;
+ guint jingle_handler_id;
+ GHashTable *content_types;
+ GHashTable *transports;
+
+ /* instances of SESSION_MAP_KEY_FORMAT => WockyJingleSession. */
+ GHashTable *sessions;
+
+ WockyJingleInfo *jingle_info;
+
+ gboolean dispose_has_run;
+};
+
+static gboolean jingle_cb (
+ WockyPorter *porter,
+ WockyStanza *msg,
+ gpointer user_data);
+static WockyJingleSession *create_session (WockyJingleFactory *fac,
+ const gchar *sid,
+ const gchar *jid,
+ WockyJingleDialect dialect,
+ gboolean local_hold);
+
+static gboolean session_query_cap_cb (
+ WockyJingleSession *session,
+ WockyContact *contact,
+ const gchar *cap_or_quirk,
+ gpointer user_data);
+static void session_terminated_cb (WockyJingleSession *sess,
+ gboolean local_terminator,
+ WockyJingleReason reason,
+ const gchar *text,
+ WockyJingleFactory *fac);
+
+static void attach_to_wocky_session (WockyJingleFactory *self);
+
+static void
+wocky_jingle_factory_init (WockyJingleFactory *obj)
+{
+ WockyJingleFactoryPrivate *priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (obj, WOCKY_TYPE_JINGLE_FACTORY,
+ WockyJingleFactoryPrivate);
+ obj->priv = priv;
+
+ priv->sessions = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+
+ priv->transports = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, NULL);
+
+ priv->content_types = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, NULL);
+
+ priv->dispose_has_run = FALSE;
+}
+
+static void
+wocky_jingle_factory_dispose (GObject *object)
+{
+ WockyJingleFactory *fac = WOCKY_JINGLE_FACTORY (object);
+ WockyJingleFactoryPrivate *priv = fac->priv;
+ GHashTableIter iter;
+ gpointer val;
+
+ if (priv->dispose_has_run)
+ return;
+
+ DEBUG ("dispose called");
+ priv->dispose_has_run = TRUE;
+
+ wocky_jingle_factory_stop (fac);
+ g_clear_object (&priv->session);
+ g_clear_object (&priv->porter);
+
+ g_hash_table_iter_init (&iter, priv->sessions);
+ while (g_hash_table_iter_next (&iter, NULL, &val))
+ g_signal_handlers_disconnect_by_func (val, session_query_cap_cb, fac);
+ g_hash_table_unref (priv->sessions);
+ priv->sessions = NULL;
+
+ g_hash_table_unref (priv->content_types);
+ priv->content_types = NULL;
+ g_hash_table_unref (priv->transports);
+ priv->transports = NULL;
+ g_clear_object (&priv->jingle_info);
+
+ if (G_OBJECT_CLASS (wocky_jingle_factory_parent_class)->dispose)
+ G_OBJECT_CLASS (wocky_jingle_factory_parent_class)->dispose (object);
+}
+
+static void
+wocky_jingle_factory_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleFactory *chan = WOCKY_JINGLE_FACTORY (object);
+ WockyJingleFactoryPrivate *priv = chan->priv;
+
+ switch (property_id) {
+ case PROP_SESSION:
+ g_value_set_object (value, priv->session);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_jingle_factory_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleFactory *chan = WOCKY_JINGLE_FACTORY (object);
+ WockyJingleFactoryPrivate *priv = chan->priv;
+
+ switch (property_id) {
+ case PROP_SESSION:
+ priv->session = g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_jingle_factory_constructed (GObject *obj)
+{
+ WockyJingleFactory *self = WOCKY_JINGLE_FACTORY (obj);
+ GObjectClass *parent = G_OBJECT_CLASS (wocky_jingle_factory_parent_class);
+
+ if (parent->constructed != NULL)
+ parent->constructed (obj);
+
+ attach_to_wocky_session (self);
+
+ jingle_media_rtp_register (self);
+ jingle_transport_google_register (self);
+ jingle_transport_rawudp_register (self);
+ jingle_transport_iceudp_register (self);
+}
+
+static void
+wocky_jingle_factory_class_init (WockyJingleFactoryClass *cls)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (cls);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (cls, sizeof (WockyJingleFactoryPrivate));
+
+ object_class->constructed = wocky_jingle_factory_constructed;
+ object_class->get_property = wocky_jingle_factory_get_property;
+ object_class->set_property = wocky_jingle_factory_set_property;
+ object_class->dispose = wocky_jingle_factory_dispose;
+
+ param_spec = g_param_spec_object ("session", "WockySession object",
+ "WockySession to listen for Jingle sessions on",
+ WOCKY_TYPE_SESSION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SESSION, param_spec);
+
+ /* signal definitions */
+
+ /*
+ * @session: a fresh new Jingle session for your listening pleasure
+ * @initiated_locally: %TRUE if this is a new outgoing session; %FALSE if it
+ * is a new incoming session
+ */
+ signals[NEW_SESSION] = g_signal_new ("new-session",
+ G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, _wocky_signals_marshal_VOID__OBJECT_BOOL,
+ G_TYPE_NONE, 2, WOCKY_TYPE_JINGLE_SESSION, G_TYPE_BOOLEAN);
+
+ /*
+ * @contact: the peer in a call
+ * @cap: the XEP-0115 feature string the session is interested in.
+ *
+ * Emitted when a Jingle session wants to check whether the peer has a
+ * particular capability. The handler should return %TRUE if @contact has
+ * @cap.
+ */
+ signals[QUERY_CAP] = g_signal_new ("query-cap",
+ G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST,
+ 0, g_signal_accumulator_first_wins, NULL,
+ _wocky_signals_marshal_BOOLEAN__OBJECT_STRING,
+ G_TYPE_BOOLEAN, 2, WOCKY_TYPE_CONTACT, G_TYPE_STRING);
+}
+
+WockyJingleFactory *
+wocky_jingle_factory_new (
+ WockySession *session)
+{
+ return g_object_new (WOCKY_TYPE_JINGLE_FACTORY,
+ "session", session,
+ NULL);
+}
+
+static void
+attach_to_wocky_session (WockyJingleFactory *self)
+{
+ WockyJingleFactoryPrivate *priv = self->priv;
+
+ g_assert (priv->session != NULL);
+
+ g_assert (priv->porter == NULL);
+ priv->porter = g_object_ref (wocky_session_get_porter (priv->session));
+
+ /* TODO: we could match different dialects here maybe? */
+ priv->jingle_handler_id = wocky_porter_register_handler_from_anyone (
+ priv->porter,
+ WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET,
+ WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, jingle_cb, self,
+ NULL);
+
+ priv->jingle_info = wocky_jingle_info_new (priv->porter);
+}
+
+void
+wocky_jingle_factory_stop (WockyJingleFactory *self)
+{
+ WockyJingleFactoryPrivate *priv = self->priv;
+
+ if (priv->porter != NULL &&
+ priv->jingle_handler_id != 0)
+ {
+ wocky_porter_unregister_handler (priv->porter, priv->jingle_handler_id);
+ priv->jingle_handler_id = 0;
+ }
+}
+
+/* The 'session' map is keyed by:
+ * "<peer's jid>\n<session id>"
+ */
+#define SESSION_MAP_KEY_FORMAT "%s\n%s"
+
+static gchar *
+make_session_map_key (
+ const gchar *jid,
+ const gchar *sid)
+{
+ return g_strdup_printf (SESSION_MAP_KEY_FORMAT, jid, sid);
+}
+
+static gchar *
+get_unique_sid_for (WockyJingleFactory *factory,
+ const gchar *jid,
+ gchar **key)
+{
+ guint32 val;
+ gchar *sid = NULL;
+ gchar *key_ = NULL;
+
+ do
+ {
+ val = g_random_int_range (1000000, G_MAXINT);
+
+ g_free (sid);
+ g_free (key_);
+ sid = g_strdup_printf ("%u", val);
+ key_ = make_session_map_key (jid, sid);
+ }
+ while (g_hash_table_lookup (factory->priv->sessions, key_) != NULL);
+
+ *key = key_;
+ return sid;
+}
+
+static WockyJingleSession *
+ensure_session (WockyJingleFactory *self,
+ const gchar *sid,
+ const gchar *from,
+ WockyJingleAction action,
+ WockyJingleDialect dialect,
+ gboolean *new_session,
+ GError **error)
+{
+ WockyJingleFactoryPrivate *priv = self->priv;
+ gchar *key;
+ WockyJingleSession *sess;
+
+ if (!wocky_decode_jid (from, NULL, NULL, NULL))
+ {
+ g_prefix_error (error, "Couldn't parse sender '%s': ", from);
+ return NULL;
+ }
+
+ /* If we can ensure the handle, we can decode the jid */
+ key = make_session_map_key (from, sid);
+ sess = g_hash_table_lookup (priv->sessions, key);
+ g_free (key);
+
+ if (sess == NULL)
+ {
+ if (action == WOCKY_JINGLE_ACTION_SESSION_INITIATE)
+ {
+ sess = create_session (self, sid, from, dialect, FALSE);
+ *new_session = TRUE;
+ }
+ else
+ {
+ g_set_error (error, WOCKY_JINGLE_ERROR,
+ WOCKY_JINGLE_ERROR_UNKNOWN_SESSION,
+ "session %s is unknown", sid);
+ return NULL;
+ }
+ }
+ else
+ {
+ *new_session = FALSE;
+ }
+
+ return sess;
+}
+
+static gboolean
+jingle_cb (
+ WockyPorter *porter,
+ WockyStanza *msg,
+ gpointer user_data)
+{
+ WockyJingleFactory *self = WOCKY_JINGLE_FACTORY (user_data);
+ GError *error = NULL;
+ const gchar *sid, *from;
+ WockyJingleSession *sess;
+ gboolean new_session = FALSE;
+ WockyJingleAction action;
+ WockyJingleDialect dialect;
+
+ /* see if it's a jingle message and detect dialect */
+ sid = wocky_jingle_session_detect (msg, &action, &dialect);
+ from = wocky_stanza_get_from (msg);
+
+ if (sid == NULL || from == NULL)
+ return FALSE;
+
+ sess = ensure_session (self, sid, from, action, dialect, &new_session,
+ &error);
+
+ if (sess == NULL)
+ goto REQUEST_ERROR;
+
+ /* now act on the message */
+ if (!wocky_jingle_session_parse (sess, action, msg, &error))
+ goto REQUEST_ERROR;
+
+ /* This has to be after the call to parse(), not inside create_session():
+ * until the session has parsed the session-initiate stanza, it does not know
+ * about its own contents, and we don't even know if the content types are
+ * something we understand. So it's essentially half-alive and useless to
+ * signal listeners.
+ */
+ if (new_session)
+ g_signal_emit (self, signals[NEW_SESSION], 0, sess, FALSE);
+
+ /* all went well, we can acknowledge the IQ */
+ wocky_porter_acknowledge_iq (porter, msg, NULL);
+
+ return TRUE;
+
+REQUEST_ERROR:
+ g_assert (error != NULL);
+ DEBUG ("NAKing with error: %s", error->message);
+ wocky_porter_send_iq_gerror (porter, msg, error);
+ g_error_free (error);
+
+ if (sess != NULL && new_session)
+ wocky_jingle_session_terminate (sess, WOCKY_JINGLE_REASON_UNKNOWN, NULL, NULL);
+
+ return TRUE;
+}
+
+static gboolean
+session_query_cap_cb (
+ WockyJingleSession *session,
+ WockyContact *contact,
+ const gchar *cap_or_quirk,
+ gpointer user_data)
+{
+ WockyJingleFactory *self = WOCKY_JINGLE_FACTORY (user_data);
+ gboolean ret;
+
+ /* Propagate the query out to the application. We can't depend on the
+ * application connecting to ::query-cap on the session because caps queries
+ * may happen while parsing the session-initiate stanza, which must happen
+ * before the session is announced to the application.
+ */
+ g_signal_emit (self, signals[QUERY_CAP], 0, contact, cap_or_quirk, &ret);
+ return ret;
+}
+
+/*
+ * If sid is set to NULL a unique sid is generated and
+ * the "local-initiator" property of the newly created
+ * WockyJingleSession is set to true.
+ */
+static WockyJingleSession *
+create_session (WockyJingleFactory *fac,
+ const gchar *sid,
+ const gchar *jid,
+ WockyJingleDialect dialect,
+ gboolean local_hold)
+{
+ WockyJingleFactoryPrivate *priv = fac->priv;
+ WockyJingleSession *sess;
+ gboolean local_initiator;
+ gchar *sid_, *key;
+ gpointer contact;
+ WockyContactFactory *factory;
+
+ factory = wocky_session_get_contact_factory (priv->session);
+ g_assert (jid != NULL);
+
+ if (strchr (jid, '/') != NULL)
+ contact = wocky_contact_factory_ensure_resource_contact (factory, jid);
+ else
+ contact = wocky_contact_factory_ensure_bare_contact (factory, jid);
+
+ g_return_val_if_fail (contact != NULL, NULL);
+ g_return_val_if_fail (WOCKY_IS_CONTACT (contact), NULL);
+
+ if (sid != NULL)
+ {
+ key = make_session_map_key (jid, sid);
+ sid_ = g_strdup (sid);
+
+ local_initiator = FALSE;
+ }
+ else
+ {
+ sid_ = get_unique_sid_for (fac, jid, &key);
+
+ local_initiator = TRUE;
+ }
+
+ /* Either we should have found the existing session when the IQ arrived, or
+ * get_unique_sid_for should have ensured the key is fresh. */
+ g_assert (NULL == g_hash_table_lookup (priv->sessions, key));
+
+ sess = wocky_jingle_session_new (
+ fac,
+ priv->porter,
+ sid_, local_initiator, contact, dialect, local_hold);
+ g_signal_connect (sess, "terminated",
+ (GCallback) session_terminated_cb, fac);
+
+ /* Takes ownership of key */
+ g_hash_table_insert (priv->sessions, key, sess);
+
+ DEBUG ("new session (%s, %s) @ %p", jid, sid_, sess);
+
+ g_free (sid_);
+ g_object_unref (contact);
+
+ g_signal_connect (sess, "query-cap",
+ (GCallback) session_query_cap_cb, (GObject *) fac);
+
+ return sess;
+}
+
+
+/**
+ * wocky_jingle_factory_create_session:
+ * @fac: the factory
+ * @jid: the full JID (typically including a resource) to establish a session
+ * with
+ * @dialect: the variant of the Jingle protocol to use
+ * @local_hold: whether the call should start out on hold; if in doubt, pass %FALSE
+ *
+ * Creates a new #WockyJingleSession to the specified contact. Note that the
+ * session will not be initiated until at least one content is added with
+ * wocky_jingle_session_add_content(), and those contents are ready.
+ *
+ * You would typically determine which @dialect to use from the peer's
+ * capabilities.
+ *
+ * Returns: (transfer none): the new session, which will not be %NULL
+ */
+WockyJingleSession *
+wocky_jingle_factory_create_session (WockyJingleFactory *fac,
+ const gchar *jid,
+ WockyJingleDialect dialect,
+ gboolean local_hold)
+{
+ WockyJingleSession *session = create_session (fac, NULL, jid, dialect, local_hold);
+
+ g_signal_emit (fac, signals[NEW_SESSION], 0, session, TRUE);
+ return session;
+}
+
+void
+wocky_jingle_factory_register_transport (WockyJingleFactory *self,
+ gchar *xmlns,
+ GType transport_type)
+{
+ g_return_if_fail (g_type_is_a (transport_type,
+ WOCKY_TYPE_JINGLE_TRANSPORT_IFACE));
+
+ g_hash_table_insert (self->priv->transports, xmlns,
+ GSIZE_TO_POINTER (transport_type));
+}
+
+GType
+wocky_jingle_factory_lookup_transport (WockyJingleFactory *self,
+ const gchar *xmlns)
+{
+ return GPOINTER_TO_SIZE (g_hash_table_lookup (self->priv->transports,
+ xmlns));
+}
+
+void
+wocky_jingle_factory_register_content_type (WockyJingleFactory *self,
+ gchar *xmlns,
+ GType content_type)
+{
+ g_return_if_fail (g_type_is_a (content_type, WOCKY_TYPE_JINGLE_CONTENT));
+
+ g_hash_table_insert (self->priv->content_types, xmlns,
+ GSIZE_TO_POINTER (content_type));
+}
+
+GType
+wocky_jingle_factory_lookup_content_type (WockyJingleFactory *self,
+ const gchar *xmlns)
+{
+ return GPOINTER_TO_SIZE (g_hash_table_lookup (self->priv->content_types,
+ xmlns));
+}
+
+static void
+session_terminated_cb (WockyJingleSession *session,
+ gboolean local_terminator G_GNUC_UNUSED,
+ WockyJingleReason reason G_GNUC_UNUSED,
+ const gchar *text G_GNUC_UNUSED,
+ WockyJingleFactory *factory)
+{
+ gchar *key = make_session_map_key (
+ wocky_jingle_session_get_peer_jid (session),
+ wocky_jingle_session_get_sid (session));
+
+ DEBUG ("removing terminated session with key %s", key);
+
+ g_signal_handlers_disconnect_by_func (session, session_query_cap_cb, factory);
+ g_warn_if_fail (g_hash_table_remove (factory->priv->sessions, key));
+
+ g_free (key);
+}
+
+WockyJingleInfo *
+wocky_jingle_factory_get_jingle_info (
+ WockyJingleFactory *self)
+{
+ return self->priv->jingle_info;
+}
diff --git a/wocky/wocky-jingle-factory.h b/wocky/wocky-jingle-factory.h
new file mode 100644
index 0000000..7d87489
--- /dev/null
+++ b/wocky/wocky-jingle-factory.h
@@ -0,0 +1,93 @@
+/*
+ * wocky-jingle-factory.h - Header for WockyJingleFactory
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#if !defined (WOCKY_H_INSIDE) && !defined (WOCKY_COMPILATION)
+# error "Only <wocky/wocky.h> can be included directly."
+#endif
+
+#ifndef __JINGLE_FACTORY_H__
+#define __JINGLE_FACTORY_H__
+
+#include <glib-object.h>
+
+#include "wocky-jingle-info.h"
+#include "wocky-jingle-types.h"
+
+G_BEGIN_DECLS
+
+typedef struct _WockyJingleFactoryClass WockyJingleFactoryClass;
+
+GType wocky_jingle_factory_get_type (void);
+
+/* TYPE MACROS */
+#define WOCKY_TYPE_JINGLE_FACTORY \
+ (wocky_jingle_factory_get_type ())
+#define WOCKY_JINGLE_FACTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), WOCKY_TYPE_JINGLE_FACTORY, \
+ WockyJingleFactory))
+#define WOCKY_JINGLE_FACTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), WOCKY_TYPE_JINGLE_FACTORY, \
+ WockyJingleFactoryClass))
+#define WOCKY_IS_JINGLE_FACTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), WOCKY_TYPE_JINGLE_FACTORY))
+#define WOCKY_IS_JINGLE_FACTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), WOCKY_TYPE_JINGLE_FACTORY))
+#define WOCKY_JINGLE_FACTORY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), WOCKY_TYPE_JINGLE_FACTORY, \
+ WockyJingleFactoryClass))
+
+struct _WockyJingleFactoryClass {
+ GObjectClass parent_class;
+};
+
+typedef struct _WockyJingleFactoryPrivate WockyJingleFactoryPrivate;
+
+struct _WockyJingleFactory {
+ GObject parent;
+
+ WockyJingleFactoryPrivate *priv;
+};
+
+WockyJingleFactory *wocky_jingle_factory_new (
+ WockySession *session);
+
+void wocky_jingle_factory_stop (WockyJingleFactory *self);
+
+void wocky_jingle_factory_register_content_type (WockyJingleFactory *self,
+ gchar *xmlns, GType content_type);
+GType wocky_jingle_factory_lookup_content_type (WockyJingleFactory *self,
+ const gchar *xmlns);
+
+void wocky_jingle_factory_register_transport (WockyJingleFactory *self,
+ gchar *xmlns, GType transport_type);
+GType wocky_jingle_factory_lookup_transport (WockyJingleFactory *self,
+ const gchar *xmlns);
+
+WockyJingleSession *wocky_jingle_factory_create_session (
+ WockyJingleFactory *fac,
+ const gchar *jid,
+ WockyJingleDialect dialect,
+ gboolean local_hold);
+
+WockyJingleInfo *wocky_jingle_factory_get_jingle_info (
+ WockyJingleFactory *fac);
+
+G_END_DECLS
+
+#endif /* __JINGLE_FACTORY_H__ */
+
diff --git a/wocky/wocky-jingle-info-internal.h b/wocky/wocky-jingle-info-internal.h
new file mode 100644
index 0000000..b38199d
--- /dev/null
+++ b/wocky/wocky-jingle-info-internal.h
@@ -0,0 +1,32 @@
+/*
+ * wocky-jingle-info-internal.h - internal types for WockyJingleInfo
+ * Copyright © 2012 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#if !defined (WOCKY_COMPILATION)
+# error "This is an internal header."
+#endif
+
+#ifndef WOCKY_JINGLE_INFO_INTERNAL_H
+#define WOCKY_JINGLE_INFO_INTERNAL_H
+
+typedef enum {
+ WOCKY_STUN_SERVER_USER_SPECIFIED,
+ WOCKY_STUN_SERVER_DISCOVERED,
+ WOCKY_STUN_SERVER_FALLBACK
+} WockyStunServerSource;
+
+#endif /* WOCKY_JINGLE_INFO_INTERNAL_H */
diff --git a/wocky/wocky-jingle-info.c b/wocky/wocky-jingle-info.c
new file mode 100644
index 0000000..07b6d28
--- /dev/null
+++ b/wocky/wocky-jingle-info.c
@@ -0,0 +1,731 @@
+/*
+ * wocky-jingle-info.c - exciting times with Google's jingleinfo extension
+ * Copyright © 2008–2012 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "wocky-jingle-info.h"
+#include "wocky-jingle-info-internal.h"
+
+#include <stdlib.h>
+
+#define WOCKY_DEBUG_FLAG WOCKY_DEBUG_JINGLE
+#include "wocky-debug-internal.h"
+#include "wocky-google-relay.h"
+#include "wocky-enumtypes.h"
+#include "wocky-signals-marshal.h"
+#include "wocky-namespaces.h"
+#include "wocky-utils.h"
+#include "wocky-c2s-porter.h"
+
+static gboolean jingle_info_cb (
+ WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data);
+
+struct _WockyJingleInfoPrivate {
+ WockyPorter *porter;
+ guint jingle_info_handler_id;
+ gchar *jid_domain;
+
+ WockyGoogleRelayResolver *google_resolver;
+
+ WockyStunServer *stun_server;
+ WockyStunServer *fallback_stun_server;
+
+ gchar *relay_token;
+
+ /* TRUE if the user has not explicitly specified a STUN server, and hence
+ * we should ask the XMPP server for one; FALSE if not.
+ */
+ gboolean get_stun_from_jingle;
+
+ gchar *relay_server;
+ guint16 relay_http_port;
+ guint16 relay_udp;
+ guint16 relay_tcp;
+ guint16 relay_ssltcp;
+
+};
+
+enum {
+ PROP_PORTER = 1,
+};
+
+enum {
+ STUN_SERVER_CHANGED = 0,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+static gboolean test_mode = FALSE;
+
+void
+wocky_jingle_info_set_test_mode (void)
+{
+ test_mode = TRUE;
+}
+
+static WockyStunServer *
+wocky_stun_server_new (
+ gchar *address,
+ guint16 port)
+{
+ WockyStunServer stun_server = { address, port };
+
+ return g_slice_dup (WockyStunServer, &stun_server);
+}
+
+static void
+wocky_stun_server_free (WockyStunServer *stun_server)
+{
+ if (stun_server != NULL)
+ {
+ g_free (stun_server->address);
+ g_slice_free (WockyStunServer, stun_server);
+ }
+}
+
+G_DEFINE_TYPE (WockyJingleInfo, wocky_jingle_info, G_TYPE_OBJECT)
+
+static void
+wocky_jingle_info_init (WockyJingleInfo *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, WOCKY_TYPE_JINGLE_INFO,
+ WockyJingleInfoPrivate);
+
+ self->priv->relay_http_port = 80;
+ self->priv->get_stun_from_jingle = TRUE;
+}
+
+static void
+wocky_jingle_info_get_property (
+ GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleInfo *self = WOCKY_JINGLE_INFO (object);
+ WockyJingleInfoPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_PORTER:
+ g_value_set_object (value, priv->porter);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+wocky_jingle_info_set_property (
+ GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleInfo *self = WOCKY_JINGLE_INFO (object);
+ WockyJingleInfoPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_PORTER:
+ g_assert (priv->porter == NULL);
+ priv->porter = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+wocky_jingle_info_constructed (GObject *object)
+{
+ WockyJingleInfo *self = WOCKY_JINGLE_INFO (object);
+ WockyJingleInfoPrivate *priv = self->priv;
+ GObjectClass *parent_class = wocky_jingle_info_parent_class;
+
+ if (parent_class->constructed != NULL)
+ parent_class->constructed (object);
+
+ g_assert (priv->porter != NULL);
+
+ if (!wocky_decode_jid (wocky_porter_get_bare_jid (priv->porter), NULL,
+ &priv->jid_domain, NULL))
+ g_assert_not_reached ();
+}
+
+static void
+wocky_jingle_info_dispose (GObject *object)
+{
+ WockyJingleInfo *self = WOCKY_JINGLE_INFO (object);
+ WockyJingleInfoPrivate *priv = self->priv;
+ GObjectClass *parent_class = wocky_jingle_info_parent_class;
+
+ if (priv->porter != NULL)
+ {
+ if (priv->jingle_info_handler_id != 0)
+ wocky_porter_unregister_handler (priv->porter,
+ priv->jingle_info_handler_id);
+
+ g_clear_object (&priv->porter);
+ }
+
+ if (priv->google_resolver != NULL)
+ {
+ wocky_google_relay_resolver_destroy (priv->google_resolver);
+ priv->google_resolver = NULL;
+ }
+
+ g_free (priv->jid_domain);
+ priv->jid_domain = NULL;
+ wocky_stun_server_free (priv->stun_server);
+ priv->stun_server = NULL;
+ wocky_stun_server_free (priv->fallback_stun_server);
+ priv->fallback_stun_server = NULL;
+ g_free (priv->relay_token);
+ priv->relay_token = NULL;
+ g_free (priv->relay_server);
+ priv->relay_server = NULL;
+
+ if (parent_class->dispose != NULL)
+ parent_class->dispose (object);
+}
+
+static void
+wocky_jingle_info_class_init (WockyJingleInfoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ object_class->get_property = wocky_jingle_info_get_property;
+ object_class->set_property = wocky_jingle_info_set_property;
+ object_class->constructed = wocky_jingle_info_constructed;
+ object_class->dispose = wocky_jingle_info_dispose;
+
+ g_type_class_add_private (klass, sizeof (WockyJingleInfoPrivate));
+
+ param_spec = g_param_spec_object ("porter", "WockyC2SPorter",
+ "Porter for the current connection",
+ WOCKY_TYPE_C2S_PORTER,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_PORTER, param_spec);
+
+ signals[STUN_SERVER_CHANGED] = g_signal_new ("stun-server-changed",
+ G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, _wocky_signals_marshal_VOID__STRING_UINT,
+ G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_UINT);
+}
+
+WockyJingleInfo *
+wocky_jingle_info_new (
+ WockyPorter *porter)
+{
+ return g_object_new (WOCKY_TYPE_JINGLE_INFO,
+ "porter", porter,
+ NULL);
+}
+
+typedef struct {
+ WockyJingleInfo *factory;
+ gchar *stun_server;
+ guint16 stun_port;
+ WockyStunServerSource source;
+ GCancellable *cancellable;
+} PendingStunServer;
+
+static void
+pending_stun_server_free (gpointer p)
+{
+ PendingStunServer *data = p;
+
+ if (data->factory != NULL)
+ g_object_remove_weak_pointer (G_OBJECT (data->factory),
+ (gpointer)&data->factory);
+
+ g_object_unref (data->cancellable);
+ g_free (data->stun_server);
+ g_slice_free (PendingStunServer, p);
+}
+
+static void
+stun_server_resolved_cb (GObject *resolver,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ PendingStunServer *data = user_data;
+ WockyJingleInfo *self = data->factory;
+ WockyJingleInfoPrivate *priv = self->priv;
+ GError *e = NULL;
+ WockyStunServer *stun_server;
+ gchar *address;
+ GList *entries;
+
+ if (self != NULL)
+ g_object_weak_unref (G_OBJECT (self),
+ (GWeakNotify)g_cancellable_cancel, data->cancellable);
+
+ entries = g_resolver_lookup_by_name_finish (
+ G_RESOLVER (resolver), result, &e);
+
+ if (entries == NULL)
+ {
+ DEBUG ("Failed to resolve STUN server %s:%u: %s",
+ data->stun_server, data->stun_port, e->message);
+ g_error_free (e);
+ goto out;
+ }
+
+ address = g_inet_address_to_string (entries->data);
+ g_resolver_free_addresses (entries);
+
+ DEBUG ("Resolved STUN server %s:%u to %s:%u", data->stun_server,
+ data->stun_port, address, data->stun_port);
+
+ if (self == NULL)
+ {
+ g_free (address);
+ goto out;
+ }
+
+ stun_server = wocky_stun_server_new (address, data->stun_port);
+
+ if (data->source == WOCKY_STUN_SERVER_FALLBACK)
+ {
+ wocky_stun_server_free (priv->fallback_stun_server);
+ priv->fallback_stun_server = stun_server;
+ }
+ else
+ {
+ wocky_stun_server_free (priv->stun_server);
+ priv->stun_server = stun_server;
+
+ g_signal_emit (self, signals[STUN_SERVER_CHANGED], 0,
+ stun_server, data->stun_port);
+ }
+
+out:
+ pending_stun_server_free (data);
+ g_object_unref (resolver);
+}
+
+static void
+wocky_jingle_info_take_stun_server_internal (
+ WockyJingleInfo *self,
+ gchar *stun_server,
+ guint16 stun_port,
+ WockyStunServerSource source)
+{
+ GResolver *resolver;
+ PendingStunServer *data;
+
+ if (stun_server == NULL)
+ return;
+
+ if (source == WOCKY_STUN_SERVER_USER_SPECIFIED)
+ self->priv->get_stun_from_jingle = FALSE;
+
+ resolver = g_resolver_get_default ();
+ data = g_slice_new0 (PendingStunServer);
+
+ DEBUG ("Resolving %s STUN server %s:%u",
+ wocky_enum_to_nick (WOCKY_TYPE_STUN_SERVER_SOURCE, data->source),
+ stun_server, stun_port);
+ data->factory = self;
+ g_object_add_weak_pointer (G_OBJECT (self), (gpointer *) &data->factory);
+ data->stun_server = stun_server;
+ data->stun_port = stun_port;
+ data->source = source;
+
+ data->cancellable = g_cancellable_new ();
+ g_object_weak_ref (G_OBJECT (self), (GWeakNotify)g_cancellable_cancel,
+ data->cancellable);
+
+ g_resolver_lookup_by_name_async (resolver, stun_server,
+ data->cancellable, stun_server_resolved_cb, data);
+}
+
+/*
+ * wocky_jingle_info_take_stun_server:
+ * @self: a #WockyJingleInfo object
+ * @stun_server: (transfer full): the STUN server's address
+ * @stun_port: the STUN server's port
+ * @is_fallback: %TRUE if this is a last resort; %FALSE if this STUN server was
+ * provided by the user (whether by explicitly setting one, or by asking the
+ * user's XMPP server).
+ */
+void
+wocky_jingle_info_take_stun_server (
+ WockyJingleInfo *self,
+ gchar *stun_server,
+ guint16 stun_port,
+ gboolean is_fallback)
+{
+ WockyStunServerSource source = is_fallback
+ ? WOCKY_STUN_SERVER_FALLBACK
+ : WOCKY_STUN_SERVER_USER_SPECIFIED;
+
+ wocky_jingle_info_take_stun_server_internal (self, stun_server, stun_port,
+ source);
+}
+
+static void
+got_jingle_info_stanza (
+ WockyJingleInfo *self,
+ WockyStanza *stanza)
+{
+ WockyNode *node, *query_node;
+
+ query_node = wocky_node_get_child_ns (
+ wocky_stanza_get_top_node (stanza), "query", WOCKY_XMPP_NS_GOOGLE_JINGLE_INFO);
+
+ if (query_node == NULL)
+ return;
+
+ if (self->priv->get_stun_from_jingle)
+ node = wocky_node_get_child (query_node, "stun");
+ else
+ node = NULL;
+
+ if (node != NULL)
+ {
+ WockyNodeIter iter;
+
+ /* TODO: use more than just the first stun server returned. */
+ wocky_node_iter_init (&iter, node, "server", NULL);
+ if (wocky_node_iter_next (&iter, &node))
+ {
+ const gchar *server;
+ const gchar *port_attr;
+ guint port = 0;
+
+ server = wocky_node_get_attribute (node, "host");
+ port_attr = wocky_node_get_attribute (node, "udp");
+
+ if (port_attr != NULL)
+ port = atoi (port_attr);
+
+ if (server != NULL &&
+ port_attr != NULL && port > 0 && port <= G_MAXUINT16)
+ {
+ DEBUG ("jingle info: got stun server %s, port %u", server,
+ port);
+ wocky_jingle_info_take_stun_server_internal (self,
+ g_strdup (server), port, WOCKY_STUN_SERVER_DISCOVERED);
+ }
+ }
+ }
+
+#ifdef ENABLE_GOOGLE_RELAY
+ node = wocky_node_get_child (query_node, "relay");
+
+ if (node != NULL)
+ {
+ WockyNode *subnode = wocky_node_get_child (node, "token");
+
+ if (subnode != NULL)
+ {
+ const gchar *token = subnode->content;
+
+ if (token != NULL)
+ {
+ DEBUG ("jingle info: got Google relay token %s", token);
+ g_free (self->priv->relay_token);
+ self->priv->relay_token = g_strdup (token);
+ }
+ }
+
+ subnode = wocky_node_get_child (node, "server");
+
+ if (subnode != NULL)
+ {
+ const gchar *server;
+ const gchar *port;
+
+ server = wocky_node_get_attribute (subnode, "host");
+
+ if (server != NULL)
+ {
+ DEBUG ("jingle info: got relay server %s", server);
+ g_free (self->priv->relay_server);
+ self->priv->relay_server = g_strdup (server);
+ }
+
+ if (test_mode)
+ {
+ /* this is not part of the real protocol, but we can't listen on
+ * port 80 in an unprivileged regression test */
+ port = wocky_node_get_attribute (subnode,
+ "gabble-test-http-port");
+
+ if (port != NULL)
+ {
+ DEBUG ("jingle info: diverting 'Google' HTTP requests to "
+ "port %s", port);
+ self->priv->relay_http_port = atoi (port);
+ }
+ }
+
+ /* FIXME: these are not really actually used anywhere at
+ * the moment, because we get the same info when creating
+ * relay session. */
+ port = wocky_node_get_attribute (subnode, "udp");
+
+ if (port != NULL)
+ {
+ DEBUG ("jingle info: got relay udp port %s", port);
+ self->priv->relay_udp = atoi (port);
+ }
+
+ port = wocky_node_get_attribute (subnode, "tcp");
+
+ if (port != NULL)
+ {
+ DEBUG ("jingle info: got relay tcp port %s", port);
+ self->priv->relay_tcp = atoi (port);
+ }
+
+ port = wocky_node_get_attribute (subnode, "tcpssl");
+
+ if (port != NULL)
+ {
+ DEBUG ("jingle info: got relay tcpssl port %s", port);
+ self->priv->relay_ssltcp = atoi (port);
+ }
+
+ }
+
+ }
+#endif /* ENABLE_GOOGLE_RELAY */
+}
+
+static gboolean
+jingle_info_cb (
+ WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ WockyJingleInfo *self = WOCKY_JINGLE_INFO (user_data);
+
+ got_jingle_info_stanza (self, stanza);
+ wocky_porter_acknowledge_iq (porter, stanza, NULL);
+
+ return TRUE;
+}
+
+static void
+jingle_info_reply_cb (
+ GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source);
+ WockyJingleInfo *self = WOCKY_JINGLE_INFO (user_data);
+ WockyStanza *reply = NULL;
+ GError *error = NULL;
+
+ reply = wocky_porter_send_iq_finish (porter, result, &error);
+ if (reply != NULL &&
+ !wocky_stanza_extract_errors (reply, NULL, &error, NULL, NULL))
+ {
+ got_jingle_info_stanza (self, reply);
+ }
+ else
+ {
+ DEBUG ("jingle info request failed: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ g_clear_object (&reply);
+ g_object_unref (self);
+}
+
+static void
+wocky_jingle_info_send_google_request (
+ WockyJingleInfo *self)
+{
+ WockyJingleInfoPrivate *priv = self->priv;
+ WockyStanza *stanza = wocky_stanza_build (
+ WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_GET, NULL,
+ wocky_porter_get_bare_jid (priv->porter),
+ '(', "query", ':', WOCKY_XMPP_NS_GOOGLE_JINGLE_INFO, ')', NULL);
+
+ wocky_porter_send_iq_async (priv->porter, stanza, NULL, jingle_info_reply_cb,
+ g_object_ref (self));
+ g_object_unref (stanza);
+
+ priv->jingle_info_handler_id = wocky_c2s_porter_register_handler_from_server (
+ WOCKY_C2S_PORTER (priv->porter),
+ WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET,
+ WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, jingle_info_cb, self,
+ '(', "query", ':', WOCKY_XMPP_NS_GOOGLE_JINGLE_INFO, ')', NULL);
+}
+
+static void
+discover_stun_servers_cb (GObject *resolver,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyJingleInfo *self = WOCKY_JINGLE_INFO (user_data);
+ WockyJingleInfoPrivate *priv = self->priv;
+ GError *error = NULL;
+ GList *targets;
+
+ targets = g_resolver_lookup_service_finish (G_RESOLVER (resolver),
+ result, &error);
+
+ if (error != NULL)
+ {
+ DEBUG ("Failed to discover STUN servers on %s: %s",
+ priv->jid_domain, error->message);
+ g_clear_error (&error);
+ }
+ else
+ {
+ DEBUG ("Discovered %d STUN servers on %s", g_list_length (targets),
+ priv->jid_domain);
+
+ /* TODO: use more than just the first. */
+ if (targets != NULL)
+ {
+ GSrvTarget *target = targets->data;
+ const gchar *hostname = g_srv_target_get_hostname (target);
+ guint16 port = g_srv_target_get_port (target);
+
+ DEBUG ("Found STUN server: %s:%d", hostname, port);
+
+ wocky_jingle_info_take_stun_server (self, g_strdup (hostname), port,
+ FALSE);
+ }
+
+ g_resolver_free_targets (targets);
+ }
+
+ g_object_unref (resolver);
+ g_object_unref (self);
+}
+
+static void
+wocky_jingle_info_lookup_srv (
+ WockyJingleInfo *self)
+{
+ WockyJingleInfoPrivate *priv = self->priv;
+ GResolver *resolver;
+
+ g_assert (priv->jid_domain != NULL);
+ DEBUG ("Discovering STUN servers on %s", priv->jid_domain);
+
+ resolver = g_resolver_get_default ();
+ g_resolver_lookup_service_async (resolver, "stun", "udp", priv->jid_domain,
+ NULL, discover_stun_servers_cb, g_object_ref (self));
+}
+
+void
+wocky_jingle_info_send_request (
+ WockyJingleInfo *self,
+ gboolean google_jingleinfo_supported)
+{
+ /* FIXME: we probably don't want to send either query if the user specified a
+ * stun server (that is, get_stun_from_jingle is FALSE).
+ */
+ if (google_jingleinfo_supported)
+ wocky_jingle_info_send_google_request (self);
+ else
+ wocky_jingle_info_lookup_srv (self);
+}
+
+/*
+ * wocky_jingle_info_get_stun_servers:
+ *
+ * Grabs the currently known and resolved stun servers.
+ *
+ * Returns: (transfer container): a list of WockyJingleInfo structs
+ */
+GList *
+wocky_jingle_info_get_stun_servers (
+ WockyJingleInfo *self)
+{
+ WockyJingleInfoPrivate *priv = self->priv;
+ GQueue stun_servers = G_QUEUE_INIT;
+
+ if (priv->stun_server != NULL)
+ g_queue_push_head (&stun_servers, priv->stun_server);
+
+ /* Only add the fallback server as a last resort. */
+ if (stun_servers.length == 0 &&
+ priv->fallback_stun_server != NULL)
+ g_queue_push_tail (&stun_servers, priv->fallback_stun_server);
+
+ return stun_servers.head;
+}
+
+const gchar *
+wocky_jingle_info_get_google_relay_token (
+ WockyJingleInfo *self)
+{
+ return self->priv->relay_token;
+}
+
+WockyJingleRelay *
+wocky_jingle_relay_new (
+ WockyJingleRelayType type,
+ const gchar *ip,
+ guint port,
+ const gchar *username,
+ const gchar *password,
+ guint component)
+{
+ WockyJingleRelay ret = { type, g_strdup (ip), port, g_strdup (username),
+ g_strdup (password), component };
+
+ return g_slice_dup (WockyJingleRelay, &ret);
+}
+
+void
+wocky_jingle_relay_free (WockyJingleRelay *relay)
+{
+ g_free (relay->ip);
+ g_free (relay->username);
+ g_free (relay->password);
+ g_slice_free (WockyJingleRelay, relay);
+}
+
+void
+wocky_jingle_info_create_google_relay_session (
+ WockyJingleInfo *self,
+ guint components,
+ WockyJingleInfoRelaySessionCb callback,
+ gpointer user_data)
+{
+ WockyJingleInfoPrivate *priv = self->priv;
+
+ g_return_if_fail (callback != NULL);
+
+ if (priv->google_resolver == NULL)
+ {
+ priv->google_resolver = wocky_google_relay_resolver_new ();
+ }
+
+ wocky_google_relay_resolver_resolve (priv->google_resolver,
+ components, priv->relay_server, priv->relay_http_port, priv->relay_token,
+ callback, user_data);
+}
diff --git a/wocky/wocky-jingle-info.h b/wocky/wocky-jingle-info.h
new file mode 100644
index 0000000..41d2e21
--- /dev/null
+++ b/wocky/wocky-jingle-info.h
@@ -0,0 +1,124 @@
+/*
+ * wocky-jingle-info.h - exciting times with Google's jingleinfo extension
+ * Copyright © 2008–2012 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#if !defined (WOCKY_H_INSIDE) && !defined (WOCKY_COMPILATION)
+# error "Only <wocky/wocky.h> can be included directly."
+#endif
+
+#ifndef WOCKY_JINGLE_INFO_H
+#define WOCKY_JINGLE_INFO_H
+
+#include <glib-object.h>
+#include "wocky-porter.h"
+
+typedef struct _WockyJingleInfo WockyJingleInfo;
+typedef struct _WockyJingleInfoClass WockyJingleInfoClass;
+typedef struct _WockyJingleInfoPrivate WockyJingleInfoPrivate;
+
+struct _WockyJingleInfoClass {
+ GObjectClass parent_class;
+};
+
+struct _WockyJingleInfo {
+ GObject parent;
+
+ WockyJingleInfoPrivate *priv;
+};
+
+GType wocky_jingle_info_get_type (void);
+
+WockyJingleInfo *wocky_jingle_info_new (
+ WockyPorter *porter);
+
+void wocky_jingle_info_take_stun_server (
+ WockyJingleInfo *self,
+ gchar *stun_server,
+ guint16 stun_port,
+ gboolean is_fallback);
+void wocky_jingle_info_send_request (
+ WockyJingleInfo *self,
+ gboolean google_jingleinfo_supported);
+
+typedef struct {
+ gchar *address;
+ guint16 port;
+} WockyStunServer;
+
+GList *wocky_jingle_info_get_stun_servers (
+ WockyJingleInfo *self);
+
+const gchar *wocky_jingle_info_get_google_relay_token (
+ WockyJingleInfo *self);
+
+typedef enum {
+ WOCKY_JINGLE_RELAY_TYPE_UDP,
+ WOCKY_JINGLE_RELAY_TYPE_TCP,
+ WOCKY_JINGLE_RELAY_TYPE_TLS
+} WockyJingleRelayType;
+#define WOCKY_N_JINGLE_RELAY_TYPES 3
+
+typedef struct {
+ WockyJingleRelayType type;
+ gchar *ip;
+ guint port;
+ gchar *username;
+ gchar *password;
+ guint component;
+} WockyJingleRelay;
+
+WockyJingleRelay *wocky_jingle_relay_new (
+ WockyJingleRelayType type,
+ const gchar *ip,
+ guint port,
+ const gchar *username,
+ const gchar *password,
+ guint component);
+void wocky_jingle_relay_free (WockyJingleRelay *relay);
+
+/*
+ * @relays: (element-type WockyJingleRelay) (transfer none): a possibly-empty
+ * array of WockyJingleRelay structs.
+ */
+typedef void (*WockyJingleInfoRelaySessionCb) (
+ GPtrArray *relays,
+ gpointer user_data);
+void wocky_jingle_info_create_google_relay_session (
+ WockyJingleInfo *self,
+ guint components,
+ WockyJingleInfoRelaySessionCb callback,
+ gpointer user_data);
+
+void wocky_jingle_info_set_test_mode (void);
+
+/* TYPE MACROS */
+#define WOCKY_TYPE_JINGLE_INFO \
+ (wocky_jingle_info_get_type ())
+#define WOCKY_JINGLE_INFO(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), WOCKY_TYPE_JINGLE_INFO, WockyJingleInfo))
+#define WOCKY_JINGLE_INFO_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), WOCKY_TYPE_JINGLE_INFO,\
+ WockyJingleInfoClass))
+#define WOCKY_IS_JINGLE_INFO(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), WOCKY_TYPE_JINGLE_INFO))
+#define WOCKY_IS_JINGLE_INFO_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), WOCKY_TYPE_JINGLE_INFO))
+#define WOCKY_JINGLE_INFO_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), WOCKY_TYPE_JINGLE_INFO, \
+ WockyJingleInfoClass))
+
+#endif /* WOCKY_JINGLE_INFO_H */
diff --git a/wocky/wocky-jingle-media-rtp.c b/wocky/wocky-jingle-media-rtp.c
new file mode 100644
index 0000000..24b0a10
--- /dev/null
+++ b/wocky/wocky-jingle-media-rtp.c
@@ -0,0 +1,1591 @@
+/*
+ * wocky-jingle-media-rtp.c - Source for WockyJingleMediaRtp
+ *
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* Media/RTP content type deals with audio/video content, ie. jingle calls. It
+ * supports standard Jingle drafts (v0.15, v0.26) and Google's jingle variants
+ * (libjingle 0.3/0.4). */
+
+#include "config.h"
+#include "wocky-jingle-media-rtp.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#define WOCKY_DEBUG_FLAG WOCKY_DEBUG_JINGLE
+
+#include "wocky-debug-internal.h"
+#include "wocky-jingle-content.h"
+#include "wocky-jingle-factory.h"
+#include "wocky-jingle-session.h"
+#include "wocky-namespaces.h"
+#include "wocky-jingle-transport-google.h"
+#include "wocky-utils.h"
+
+G_DEFINE_TYPE (WockyJingleMediaRtp,
+ wocky_jingle_media_rtp, WOCKY_TYPE_JINGLE_CONTENT);
+
+/* signal enum */
+enum
+{
+ REMOTE_MEDIA_DESCRIPTION,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum
+{
+ PROP_MEDIA_TYPE = 1,
+ PROP_REMOTE_MUTE,
+ LAST_PROPERTY
+};
+
+typedef enum {
+ WOCKY_JINGLE_MEDIA_PROFILE_RTP_AVP,
+} WockyJingleMediaProfile;
+
+struct _WockyJingleMediaRtpPrivate
+{
+ WockyJingleMediaDescription *local_media_description;
+
+ /* Holds (WockyJingleCodec *)'s borrowed from local_media_description,
+ * namely codecs which have changed from local_media_description's
+ * previous value. Since the contents are borrowed, this must be
+ * freed with g_list_free, not jingle_media_rtp_free_codecs().
+ */
+ GList *local_codec_updates;
+
+ WockyJingleMediaDescription *remote_media_description;
+ WockyJingleMediaType media_type;
+ gboolean remote_mute;
+
+ gboolean has_rtcp_fb;
+ gboolean has_rtp_hdrext;
+
+ gboolean dispose_has_run;
+};
+
+static void
+wocky_jingle_media_rtp_init (WockyJingleMediaRtp *obj)
+{
+ WockyJingleMediaRtpPrivate *priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (obj, WOCKY_TYPE_JINGLE_MEDIA_RTP,
+ WockyJingleMediaRtpPrivate);
+ obj->priv = priv;
+ priv->dispose_has_run = FALSE;
+}
+
+
+/**
+ * jingle_media_rtp_codec_new:
+ * @id: a codec ID, as specified in tables 4 and 5 of RFC 3551.
+ * @name: (allow-none): the codec's name. This is optional if @id is one of the
+ * statically-defined codec identifiers, and required if @id is in the range
+ * 96–127. (This is not enforced by this library.)
+ * @clockrate: the clock rate for this codec, or 0 to not specify a clock rate.
+ * @channels: the number of channels, or 0 to leave this unspecified (which the
+ * peer should interpret as the default value, 1).
+ * @params: (element-type utf8 utf8) (transfer none) (allow-none): parameters
+ * for this codec. This is referenced, not copied, so you should avoid
+ * modifying this parameter after calling this function.
+ *
+ * Creates a new structure describing a codec, suitable for including in a
+ * #WockyJingleMediaDescription.
+ *
+ * Returns: (transfer full): the codec description.
+ */
+WockyJingleCodec *
+jingle_media_rtp_codec_new (
+ guint id,
+ const gchar *name,
+ guint clockrate,
+ guint channels,
+ GHashTable *params)
+{
+ WockyJingleCodec *p = g_slice_new0 (WockyJingleCodec);
+
+ p->id = id;
+ p->name = g_strdup (name);
+ p->clockrate = clockrate;
+ p->channels = channels;
+ p->trr_int = G_MAXUINT;
+
+ if (params != NULL)
+ {
+ g_hash_table_ref (params);
+ p->params = params;
+ }
+ else
+ {
+ p->params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+ g_free);
+ }
+
+ return p;
+}
+
+
+static GList *
+wocky_jingle_feedback_message_list_copy (GList *fbs)
+{
+ GQueue new = G_QUEUE_INIT;
+ GList *li;
+
+ for (li = fbs; li; li = li->next)
+ {
+ WockyJingleFeedbackMessage *fb = li->data;
+
+ g_queue_push_tail (&new, wocky_jingle_feedback_message_new (fb->type,
+ fb->subtype));
+ }
+
+ return new.head;
+}
+
+static void
+wocky_jingle_feedback_message_list_free (GList *fbs)
+{
+ while (fbs != NULL)
+ {
+ wocky_jingle_feedback_message_free (fbs->data);
+ fbs = g_list_delete_link (fbs, fbs);
+ }
+}
+
+void
+jingle_media_rtp_codec_free (WockyJingleCodec *p)
+{
+ g_hash_table_unref (p->params);
+ g_free (p->name);
+ wocky_jingle_feedback_message_list_free (p->feedback_msgs);
+ g_slice_free (WockyJingleCodec, p);
+}
+
+static void
+add_codec_to_table (WockyJingleCodec *codec,
+ GHashTable *table)
+{
+ g_hash_table_insert (table, GUINT_TO_POINTER ((guint) codec->id), codec);
+}
+
+static GHashTable *
+build_codec_table (GList *codecs)
+{
+ GHashTable *table = g_hash_table_new (NULL, NULL);
+
+ g_list_foreach (codecs, (GFunc) add_codec_to_table, table);
+ return table;
+}
+
+GList *
+jingle_media_rtp_copy_codecs (GList *codecs)
+{
+ GList *ret = NULL, *l;
+
+ for (l = codecs; l != NULL; l = g_list_next (l))
+ {
+ WockyJingleCodec *c = l->data;
+ WockyJingleCodec *newc = jingle_media_rtp_codec_new (c->id,
+ c->name, c->clockrate, c->channels, c->params);
+ newc->trr_int = c->trr_int;
+ ret = g_list_append (ret, newc);
+ }
+
+ return ret;
+}
+
+void
+jingle_media_rtp_free_codecs (GList *codecs)
+{
+ while (codecs != NULL)
+ {
+ jingle_media_rtp_codec_free (codecs->data);
+ codecs = g_list_delete_link (codecs, codecs);
+ }
+}
+
+static void
+wocky_jingle_media_rtp_dispose (GObject *object)
+{
+ WockyJingleMediaRtp *trans = WOCKY_JINGLE_MEDIA_RTP (object);
+ WockyJingleMediaRtpPrivate *priv = trans->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ DEBUG ("dispose called");
+ priv->dispose_has_run = TRUE;
+
+ if (priv->remote_media_description != NULL)
+ wocky_jingle_media_description_free (priv->remote_media_description);
+ priv->remote_media_description = NULL;
+
+ if (priv->local_media_description != NULL)
+ wocky_jingle_media_description_free (priv->local_media_description);
+ priv->local_media_description = NULL;
+
+ if (priv->local_codec_updates != NULL)
+ {
+ DEBUG ("We have an unsent codec parameter update! Weird.");
+
+ g_list_free (priv->local_codec_updates);
+ priv->local_codec_updates = NULL;
+ }
+
+ if (G_OBJECT_CLASS (wocky_jingle_media_rtp_parent_class)->dispose)
+ G_OBJECT_CLASS (wocky_jingle_media_rtp_parent_class)->dispose (object);
+}
+
+static void
+wocky_jingle_media_rtp_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleMediaRtp *trans = WOCKY_JINGLE_MEDIA_RTP (object);
+ WockyJingleMediaRtpPrivate *priv = trans->priv;
+
+ switch (property_id) {
+ case PROP_MEDIA_TYPE:
+ g_value_set_uint (value, priv->media_type);
+ break;
+ case PROP_REMOTE_MUTE:
+ g_value_set_boolean (value, priv->remote_mute);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_jingle_media_rtp_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleMediaRtp *trans = WOCKY_JINGLE_MEDIA_RTP (object);
+ WockyJingleMediaRtpPrivate *priv = trans->priv;
+
+ switch (property_id) {
+ case PROP_MEDIA_TYPE:
+ priv->media_type = g_value_get_uint (value);
+ break;
+ case PROP_REMOTE_MUTE:
+ priv->remote_mute = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void parse_description (WockyJingleContent *content,
+ WockyNode *desc_node, GError **error);
+static void produce_description (WockyJingleContent *obj,
+ WockyNode *content_node);
+static void transport_created (WockyJingleContent *obj,
+ WockyJingleTransportIface *transport);
+
+static void
+wocky_jingle_media_rtp_class_init (WockyJingleMediaRtpClass *cls)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (cls);
+ WockyJingleContentClass *content_class = WOCKY_JINGLE_CONTENT_CLASS (cls);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (cls, sizeof (WockyJingleMediaRtpPrivate));
+
+ object_class->get_property = wocky_jingle_media_rtp_get_property;
+ object_class->set_property = wocky_jingle_media_rtp_set_property;
+ object_class->dispose = wocky_jingle_media_rtp_dispose;
+
+ content_class->parse_description = parse_description;
+ content_class->produce_description = produce_description;
+ content_class->transport_created = transport_created;
+
+ param_spec = g_param_spec_uint ("media-type", "RTP media type",
+ "Media type.",
+ WOCKY_JINGLE_MEDIA_TYPE_NONE, G_MAXUINT32, WOCKY_JINGLE_MEDIA_TYPE_NONE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_MEDIA_TYPE, param_spec);
+
+ param_spec = g_param_spec_boolean ("remote-mute", "Remote mute",
+ "TRUE if the peer has muted this stream", FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REMOTE_MUTE, param_spec);
+
+ /* signal definitions */
+
+ /**
+ * WockyJingleMediaRtp::remote-media-description:
+ * @content: the RTP content
+ * @md: a #WockyJingleMediaDescription
+ *
+ * Emitted when the remote media description is received or subsequently updated.
+ */
+ signals[REMOTE_MEDIA_DESCRIPTION] = g_signal_new ("remote-media-description",
+ G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+}
+
+static void transport_created (WockyJingleContent *content,
+ WockyJingleTransportIface *transport)
+{
+ WockyJingleMediaRtp *self = WOCKY_JINGLE_MEDIA_RTP (content);
+ WockyJingleMediaRtpPrivate *priv = self->priv;
+ WockyJingleTransportGoogle *gtrans = NULL;
+ WockyJingleDialect dialect;
+
+ if (WOCKY_IS_JINGLE_TRANSPORT_GOOGLE (transport))
+ {
+ gtrans = WOCKY_JINGLE_TRANSPORT_GOOGLE (transport);
+ dialect = wocky_jingle_session_get_dialect (content->session);
+
+ if (priv->media_type == WOCKY_JINGLE_MEDIA_TYPE_VIDEO &&
+ (WOCKY_JINGLE_DIALECT_IS_GOOGLE (dialect) ||
+ wocky_jingle_session_peer_has_cap (content->session,
+ WOCKY_QUIRK_GOOGLE_WEBMAIL_CLIENT) ||
+ wocky_jingle_session_peer_has_cap (content->session,
+ WOCKY_QUIRK_ANDROID_GTALK_CLIENT)))
+ {
+ jingle_transport_google_set_component_name (gtrans, "video_rtp", 1);
+ jingle_transport_google_set_component_name (gtrans, "video_rtcp", 2);
+ }
+ else
+ {
+ jingle_transport_google_set_component_name (gtrans, "rtp", 1);
+ jingle_transport_google_set_component_name (gtrans, "rtcp", 2);
+ }
+ }
+}
+
+
+static WockyJingleMediaType
+extract_media_type (WockyNode *desc_node,
+ GError **error)
+{
+ if (wocky_node_has_ns (desc_node, WOCKY_XMPP_NS_JINGLE_RTP))
+ {
+ const gchar *type = wocky_node_get_attribute (desc_node, "media");
+
+ if (type == NULL)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "missing required media type attribute");
+ return WOCKY_JINGLE_MEDIA_TYPE_NONE;
+ }
+
+ if (!wocky_strdiff (type, "audio"))
+ return WOCKY_JINGLE_MEDIA_TYPE_AUDIO;
+
+ if (!wocky_strdiff (type, "video"))
+ return WOCKY_JINGLE_MEDIA_TYPE_VIDEO;
+
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "unknown media type %s", type);
+ return WOCKY_JINGLE_MEDIA_TYPE_NONE;
+ }
+
+ if (wocky_node_has_ns (desc_node, WOCKY_XMPP_NS_JINGLE_DESCRIPTION_AUDIO))
+ return WOCKY_JINGLE_MEDIA_TYPE_AUDIO;
+
+ if (wocky_node_has_ns (desc_node, WOCKY_XMPP_NS_JINGLE_DESCRIPTION_VIDEO))
+ return WOCKY_JINGLE_MEDIA_TYPE_VIDEO;
+
+ if (wocky_node_has_ns (desc_node, WOCKY_XMPP_NS_GOOGLE_SESSION_PHONE))
+ return WOCKY_JINGLE_MEDIA_TYPE_AUDIO;
+
+ if (wocky_node_has_ns (desc_node, WOCKY_XMPP_NS_GOOGLE_SESSION_VIDEO))
+ return WOCKY_JINGLE_MEDIA_TYPE_VIDEO;
+
+ /* If we get here, namespace in use is not one of namespaces we signed up
+ * with, so obviously a bug somewhere.
+ */
+ g_assert_not_reached ();
+}
+
+static WockyJingleFeedbackMessage *
+parse_rtcp_fb (WockyJingleContent *content, WockyNode *node)
+{
+ const gchar *pt_ns = wocky_node_get_ns (node);
+ const gchar *type;
+ const gchar *subtype;
+
+ if (wocky_strdiff (pt_ns, WOCKY_XMPP_NS_JINGLE_RTCP_FB))
+ return NULL;
+
+ type = wocky_node_get_attribute (node, "type");
+ if (type == NULL)
+ return NULL;
+
+ subtype = wocky_node_get_attribute (node, "subtype");
+
+ /* This is optional, defaults to "" */
+ if (subtype == NULL)
+ subtype = "";
+
+ return wocky_jingle_feedback_message_new (type, subtype);
+}
+
+
+/*
+ * Returns G_MAXUINT on error
+ */
+static guint
+parse_rtcp_fb_trr_int (WockyJingleContent *content, WockyNode *node)
+{
+ const gchar *pt_ns = wocky_node_get_ns (node);
+ const gchar *txt;
+ guint trr_int;
+ gchar *endptr = NULL;
+
+ if (wocky_strdiff (pt_ns, WOCKY_XMPP_NS_JINGLE_RTCP_FB))
+ return G_MAXUINT;
+
+ txt = wocky_node_get_attribute (node, "value");
+ if (txt == NULL)
+ return G_MAXUINT;
+
+ trr_int = strtol (txt, &endptr, 10);
+ if (endptr == NULL || endptr == txt)
+ return G_MAXUINT;
+
+ return trr_int;
+}
+
+
+/**
+ * parse_payload_type:
+ * @node: a <payload-type> node.
+ *
+ * Returns: a newly-allocated WockyJingleCodec if parsing succeeds, or %NULL
+ * otherwise.
+ */
+static WockyJingleCodec *
+parse_payload_type (WockyJingleContent *content,
+ WockyNode *node)
+{
+ WockyJingleMediaRtp *self = WOCKY_JINGLE_MEDIA_RTP (content);
+ WockyJingleMediaRtpPrivate *priv = self->priv;
+ WockyJingleCodec *p;
+ const char *txt;
+ guint8 id;
+ const gchar *name;
+ guint clockrate = 0;
+ guint channels = 0;
+ WockyNode *param;
+ WockyNodeIter i;
+
+ txt = wocky_node_get_attribute (node, "id");
+ if (txt == NULL)
+ return NULL;
+
+ id = atoi (txt);
+
+ name = wocky_node_get_attribute (node, "name");
+ if (name == NULL)
+ name = "";
+
+ /* xep-0167 v0.22, gtalk libjingle 0.3/0.4 use "clockrate" */
+ txt = wocky_node_get_attribute (node, "clockrate");
+ /* older jingle rtp used "rate" ? */
+ if (txt == NULL)
+ txt = wocky_node_get_attribute (node, "rate");
+
+ if (txt != NULL)
+ clockrate = atoi (txt);
+
+ txt = wocky_node_get_attribute (node, "channels");
+ if (txt != NULL)
+ channels = atoi (txt);
+
+ p = jingle_media_rtp_codec_new (id, name, clockrate, channels, NULL);
+
+ wocky_node_iter_init (&i, node, NULL, NULL);
+ while (wocky_node_iter_next (&i, &param))
+ {
+ if (!wocky_strdiff (param->name, "parameter"))
+ {
+ const gchar *param_name, *param_value;
+
+ param_name = wocky_node_get_attribute (param, "name");
+ param_value = wocky_node_get_attribute (param, "value");
+
+ if (param_name == NULL || param_value == NULL)
+ continue;
+
+ g_hash_table_insert (p->params, g_strdup (param_name),
+ g_strdup (param_value));
+ }
+ else if (!wocky_strdiff (param->name, "rtcp-fb"))
+ {
+ WockyJingleFeedbackMessage *fb = parse_rtcp_fb (content, param);
+
+ if (fb != NULL)
+ {
+ p->feedback_msgs = g_list_append (p->feedback_msgs, fb);
+ priv->has_rtcp_fb = TRUE;
+ }
+ }
+ else if (!wocky_strdiff (param->name,
+ "rtcp-fb-trr-int"))
+ {
+ guint trr_int = parse_rtcp_fb_trr_int (content, param);
+
+ if (trr_int != G_MAXUINT)
+ {
+ p->trr_int = trr_int;
+ priv->has_rtcp_fb = TRUE;
+ }
+ }
+ }
+
+ DEBUG ("new remote codec: id = %u, name = %s, clockrate = %u, channels = %u",
+ p->id, p->name, p->clockrate, p->channels);
+
+ return p;
+}
+
+static WockyJingleRtpHeaderExtension *
+parse_rtp_header_extension (WockyNode *node)
+{
+ guint id;
+ WockyJingleContentSenders senders;
+ const gchar *uri;
+ const char *txt;
+
+ txt = wocky_node_get_attribute (node, "id");
+ if (txt == NULL)
+ return NULL;
+
+ id = atoi (txt);
+
+ /* Only valid ranges are 1-256 and 4096-4351 */
+ if ((id < 1 || id > 256) && (id < 4096 || id > 4351))
+ return NULL;
+
+ txt = wocky_node_get_attribute (node, "senders");
+
+ if (txt == NULL || !g_ascii_strcasecmp (txt, "both"))
+ senders = WOCKY_JINGLE_CONTENT_SENDERS_BOTH;
+ else if (!g_ascii_strcasecmp (txt, "initiator"))
+ senders = WOCKY_JINGLE_CONTENT_SENDERS_INITIATOR;
+ else if (!g_ascii_strcasecmp (txt, "responder"))
+ senders = WOCKY_JINGLE_CONTENT_SENDERS_RESPONDER;
+ else
+ return NULL;
+
+ uri = wocky_node_get_attribute (node, "uri");
+
+ if (uri == NULL)
+ return NULL;
+
+ return wocky_jingle_rtp_header_extension_new (id, senders, uri);
+}
+
+
+/**
+ * codec_update_coherent:
+ * @old_c: this content's old cache of the codec, or %NULL if it hasn't heard
+ * of it.
+ * @new_c: the proposed update, whose id must equal that of @old_c if the
+ * latter is non-NULL.
+ * @domain: the error domain to set @e to if necessary
+ * @code: the error code to set @e to if necessary
+ * @e: location to hold an error
+ *
+ * Compares @old_c and @new_c, which are assumed to have the same id, to check
+ * that the name, clockrate and number of channels hasn't changed. If they
+ * have, returns %FALSE and sets @e.
+ */
+static gboolean
+codec_update_coherent (const WockyJingleCodec *old_c,
+ const WockyJingleCodec *new_c,
+ GError **e)
+{
+ const GQuark domain = WOCKY_XMPP_ERROR;
+ const gint code = WOCKY_XMPP_ERROR_BAD_REQUEST;
+
+ if (old_c == NULL)
+ {
+ g_set_error (e, domain, code, "Codec with id %u ('%s') unknown",
+ new_c->id, new_c->name);
+ return FALSE;
+ }
+
+ if (g_ascii_strcasecmp (new_c->name, old_c->name))
+ {
+ g_set_error (e, domain, code,
+ "tried to change codec %u's name from %s to %s",
+ new_c->id, old_c->name, new_c->name);
+ return FALSE;
+ }
+
+ if (new_c->clockrate != old_c->clockrate)
+ {
+ g_set_error (e, domain, code,
+ "tried to change codec %u (%s)'s clockrate from %u to %u",
+ new_c->id, new_c->name, old_c->clockrate, new_c->clockrate);
+ return FALSE;
+ }
+
+ if (old_c->channels != 0 &&
+ new_c->channels != old_c->channels)
+ {
+ g_set_error (e, domain, code,
+ "tried to change codec %u (%s)'s channels from %u to %u",
+ new_c->id, new_c->name, new_c->channels, old_c->channels);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+update_remote_media_description (WockyJingleMediaRtp *self,
+ WockyJingleMediaDescription *new_media_description,
+ GError **error)
+{
+ WockyJingleMediaRtpPrivate *priv = self->priv;
+ GHashTable *rc = NULL;
+ WockyJingleCodec *old_c, *new_c;
+ GList *l;
+ GError *e = NULL;
+
+ if (priv->remote_media_description == NULL)
+ {
+ priv->remote_media_description = new_media_description;
+ new_media_description = NULL;
+ goto out;
+ }
+
+ rc = build_codec_table (priv->remote_media_description->codecs);
+
+ /* We already know some remote codecs, so this is just the other end updating
+ * some parameters.
+ */
+ for (l = new_media_description->codecs; l != NULL; l = l->next)
+ {
+ new_c = l->data;
+ old_c = g_hash_table_lookup (rc, GUINT_TO_POINTER ((guint) new_c->id));
+
+ if (!codec_update_coherent (old_c, new_c, &e))
+ goto out;
+ }
+
+ /* Okay, all the updates are cool. Let's switch the parameters around. */
+ for (l = new_media_description->codecs; l != NULL; l = l->next)
+ {
+ GHashTable *params;
+
+ new_c = l->data;
+ old_c = g_hash_table_lookup (rc, GUINT_TO_POINTER ((guint) new_c->id));
+
+ params = old_c->params;
+ old_c->params = new_c->params;
+ new_c->params = params;
+ }
+
+out:
+ if (new_media_description != NULL)
+ wocky_jingle_media_description_free (new_media_description);
+
+ if (rc != NULL)
+ g_hash_table_unref (rc);
+
+ if (e != NULL)
+ {
+ DEBUG ("Rejecting codec update: %s", e->message);
+ g_propagate_error (error, e);
+ }
+ else
+ {
+ DEBUG ("Emitting remote-media-description signal");
+ g_signal_emit (self, signals[REMOTE_MEDIA_DESCRIPTION], 0,
+ priv->remote_media_description);
+ }
+}
+
+static void
+parse_description (WockyJingleContent *content,
+ WockyNode *desc_node, GError **error)
+{
+ WockyJingleMediaRtp *self = WOCKY_JINGLE_MEDIA_RTP (content);
+ WockyJingleMediaRtpPrivate *priv = self->priv;
+ WockyJingleMediaType mtype;
+ WockyJingleMediaDescription *md;
+ WockyJingleCodec *p;
+ WockyJingleDialect dialect = wocky_jingle_session_get_dialect (content->session);
+ gboolean video_session = FALSE;
+ WockyNodeIter i;
+ WockyNode *node;
+ gboolean description_error = FALSE;
+ gboolean is_avpf = FALSE;
+
+ DEBUG ("node: %s", desc_node->name);
+
+ if (priv->media_type == WOCKY_JINGLE_MEDIA_TYPE_NONE)
+ mtype = extract_media_type (desc_node, error);
+ else
+ mtype = priv->media_type;
+
+ if (mtype == WOCKY_JINGLE_MEDIA_TYPE_NONE)
+ return;
+
+ DEBUG ("detected media type %u", mtype);
+
+ if (dialect == WOCKY_JINGLE_DIALECT_GTALK3)
+ {
+ const gchar *desc_ns =
+ wocky_node_get_ns (desc_node);
+ video_session = !wocky_strdiff (desc_ns, WOCKY_XMPP_NS_GOOGLE_SESSION_VIDEO);
+ }
+
+ md = wocky_jingle_media_description_new ();
+
+ wocky_node_iter_init (&i, desc_node, NULL, NULL);
+ while (wocky_node_iter_next (&i, &node) && !description_error)
+ {
+ if (!wocky_strdiff (node->name, "payload-type"))
+ {
+ if (dialect == WOCKY_JINGLE_DIALECT_GTALK3)
+ {
+ const gchar *pt_ns = wocky_node_get_ns (node);
+
+ if (priv->media_type == WOCKY_JINGLE_MEDIA_TYPE_AUDIO)
+ {
+ if (video_session &&
+ wocky_strdiff (pt_ns, WOCKY_XMPP_NS_GOOGLE_SESSION_PHONE))
+ continue;
+ }
+ else if (priv->media_type == WOCKY_JINGLE_MEDIA_TYPE_VIDEO)
+ {
+ if (!(video_session && pt_ns == NULL)
+ && wocky_strdiff (pt_ns, WOCKY_XMPP_NS_GOOGLE_SESSION_VIDEO))
+ continue;
+ }
+ }
+
+ p = parse_payload_type (content, node);
+
+ if (p == NULL)
+ {
+ description_error = TRUE;
+ }
+ else
+ {
+ md->codecs = g_list_append (md->codecs, p);
+ if (p->trr_int != G_MAXUINT || p->feedback_msgs)
+ is_avpf = TRUE;
+ }
+ }
+ else if (!wocky_strdiff (node->name, "rtp-hdrext"))
+ {
+ const gchar *pt_ns = wocky_node_get_ns (node);
+ WockyJingleRtpHeaderExtension *hdrext;
+
+ if (wocky_strdiff (pt_ns, WOCKY_XMPP_NS_JINGLE_RTP_HDREXT))
+ continue;
+
+ hdrext = parse_rtp_header_extension (node);
+
+ if (hdrext == NULL)
+ {
+ description_error = TRUE;
+ }
+ else
+ {
+ md->hdrexts = g_list_append (md->hdrexts, hdrext);
+ priv->has_rtp_hdrext = TRUE;
+ }
+
+ }
+ else if (!wocky_strdiff (node->name, "rtcp-fb"))
+ {
+ WockyJingleFeedbackMessage *fb = parse_rtcp_fb (content, node);
+
+ if (fb == NULL)
+ {
+ description_error = TRUE;
+ }
+ else
+ {
+ md->feedback_msgs = g_list_append (md->feedback_msgs, fb);
+ is_avpf = TRUE;
+ priv->has_rtcp_fb = TRUE;
+ }
+ }
+ else if (!wocky_strdiff (node->name, "rtcp-fb-trr-int"))
+ {
+ guint trr_int = parse_rtcp_fb_trr_int (content, node);
+
+ if (trr_int == G_MAXUINT)
+ {
+ description_error = TRUE;
+ }
+ else
+ {
+ md->trr_int = trr_int;
+ is_avpf = TRUE;
+ priv->has_rtcp_fb = TRUE;
+ }
+ }
+ }
+
+ if (description_error)
+ {
+ /* rollback these */
+ wocky_jingle_media_description_free (md);
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "invalid description");
+ return;
+ }
+
+ /* If the profile is AVPF, the trr-int default to 0 */
+ if (is_avpf && md->trr_int == G_MAXUINT)
+ md->trr_int = 0;
+
+ priv->media_type = mtype;
+
+ update_remote_media_description (self, md, error);
+}
+
+/* The Google Talk desktop client is picky about the case of codec names, even
+ * though SDP defines them to be case-insensitive. The particular case that was
+ * causing problems was ILBC vs iLBC, but it seems safer to special-case the
+ * lot. This list is taken from the initiate sent by the desktop client on
+ * 2009-07-01.
+ */
+static const gchar * const codec_cases[] = {
+ "CN",
+ "EG711A",
+ "EG711U",
+ "G723",
+ "IPCMWB",
+ "ISAC",
+ "PCMA",
+ "PCMU",
+ "iLBC",
+ "speex",
+ "telephone-event",
+ NULL
+};
+
+static const gchar *
+gtalk_case (const gchar *codec)
+{
+ const gchar * const *ret = codec_cases;
+
+ for (; *ret != NULL; ret++)
+ if (g_ascii_strcasecmp (*ret, codec) == 0)
+ return *ret;
+
+ return codec;
+}
+
+static void
+_produce_extra_param (gpointer key, gpointer value, gpointer user_data)
+{
+ WockyNode *pt_node = user_data;
+ WockyNode *param;
+ gchar *param_name = key;
+ gchar *param_value = value;
+
+ param = wocky_node_add_child (pt_node, "parameter");
+ wocky_node_set_attribute (param, "name", param_name);
+ wocky_node_set_attribute (param, "value", param_value);
+}
+
+static void
+produce_rtcp_fb_trr_int (WockyNode *node,
+ guint trr_int)
+{
+ WockyNode *trr_int_node;
+ gchar tmp[10];
+
+ if (trr_int == G_MAXUINT || trr_int == 0)
+ return;
+
+ trr_int_node = wocky_node_add_child_ns (node, "rtcp-fb-trr-int",
+ WOCKY_XMPP_NS_JINGLE_RTCP_FB);
+ snprintf (tmp, 9, "%d", trr_int);
+ wocky_node_set_attribute (trr_int_node, "value", tmp);
+}
+
+
+static void
+produce_rtcp_fb (WockyJingleFeedbackMessage *fb, WockyNode *node)
+{
+ WockyNode *fb_node;
+
+ fb_node = wocky_node_add_child (node, "rtcp-fb");
+
+ wocky_node_set_attribute (fb_node, "xmlns", WOCKY_XMPP_NS_JINGLE_RTCP_FB);
+ wocky_node_set_attribute (fb_node, "type", fb->type);
+
+ if (fb->subtype != NULL && fb->subtype[0] != 0)
+ wocky_node_set_attribute (fb_node, "subtype", fb->subtype);
+}
+
+static void
+produce_payload_type (WockyJingleContent *content,
+ WockyNode *desc_node,
+ WockyJingleMediaType type,
+ WockyJingleCodec *p,
+ WockyJingleDialect dialect)
+{
+ WockyJingleMediaRtp *self = WOCKY_JINGLE_MEDIA_RTP (content);
+ WockyJingleMediaRtpPrivate *priv = self->priv;
+ WockyNode *pt_node;
+ gchar buf[16];
+
+ pt_node = wocky_node_add_child (desc_node, "payload-type");
+
+ /* id: required */
+ sprintf (buf, "%d", p->id);
+ wocky_node_set_attribute (pt_node, "id", buf);
+
+ if (dialect == WOCKY_JINGLE_DIALECT_GTALK3)
+ {
+ if (type == WOCKY_JINGLE_MEDIA_TYPE_AUDIO)
+ {
+ /* Gtalk 03 has either an audio or a video session, in case of a
+ * video session the audio codecs need to set their namespace to
+ * WOCKY_XMPP_NS_GOOGLE_SESSION_PHONE. In the case of an audio session it
+ * doesn't matter, so just always set the namespace on audio
+ * payloads.
+ */
+ pt_node->ns = g_quark_from_static_string (
+ WOCKY_XMPP_NS_GOOGLE_SESSION_PHONE);
+ }
+ else
+ {
+ /* If width, height and framerate aren't set the google server ignore
+ * our initiate.. These are a recv parameters, to it doesn't matter
+ * for what we're sending, just for what we're getting.. 320x240
+ * seems a sane enough default */
+ wocky_node_set_attributes (pt_node,
+ "width", "320",
+ "height", "240",
+ "framerate", "30",
+ NULL);
+ }
+
+ }
+
+ /* name: optional */
+ if (*p->name != '\0')
+ {
+ if (WOCKY_JINGLE_DIALECT_IS_GOOGLE (dialect))
+ wocky_node_set_attribute (pt_node, "name", gtalk_case (p->name));
+ else
+ wocky_node_set_attribute (pt_node, "name", p->name);
+ }
+
+ /* clock rate: optional */
+ if (p->clockrate != 0)
+ {
+ const gchar *attname = "clockrate";
+
+ if (dialect == WOCKY_JINGLE_DIALECT_V015)
+ attname = "rate";
+
+ sprintf (buf, "%u", p->clockrate);
+ wocky_node_set_attribute (pt_node, attname, buf);
+ }
+
+ if (p->channels != 0)
+ {
+ sprintf (buf, "%u", p->channels);
+ wocky_node_set_attribute (pt_node, "channels", buf);
+ }
+
+ if (p->params != NULL)
+ g_hash_table_foreach (p->params, _produce_extra_param, pt_node);
+
+
+ if (priv->has_rtcp_fb)
+ {
+ g_list_foreach (p->feedback_msgs, (GFunc) produce_rtcp_fb, pt_node);
+ produce_rtcp_fb_trr_int (pt_node, p->trr_int);
+ }
+}
+
+static WockyNode *
+produce_description_node (WockyJingleDialect dialect, WockyJingleMediaType media_type,
+ WockyNode *content_node)
+{
+ WockyNode *desc_node;
+ const gchar *xmlns = NULL, *media_attr = NULL;
+
+ if (dialect == WOCKY_JINGLE_DIALECT_GTALK3)
+ return NULL;
+
+ switch (dialect)
+ {
+ case WOCKY_JINGLE_DIALECT_GTALK4:
+ g_assert (media_type == WOCKY_JINGLE_MEDIA_TYPE_AUDIO);
+ xmlns = WOCKY_XMPP_NS_GOOGLE_SESSION_PHONE;
+ break;
+ case WOCKY_JINGLE_DIALECT_V015:
+ if (media_type == WOCKY_JINGLE_MEDIA_TYPE_AUDIO)
+ xmlns = WOCKY_XMPP_NS_JINGLE_DESCRIPTION_AUDIO;
+ else if (media_type == WOCKY_JINGLE_MEDIA_TYPE_VIDEO)
+ xmlns = WOCKY_XMPP_NS_JINGLE_DESCRIPTION_VIDEO;
+ else
+ {
+ DEBUG ("unknown media type %u", media_type);
+ xmlns = "";
+ }
+ break;
+ default:
+ xmlns = WOCKY_XMPP_NS_JINGLE_RTP;
+ if (media_type == WOCKY_JINGLE_MEDIA_TYPE_AUDIO)
+ media_attr = "audio";
+ else if (media_type == WOCKY_JINGLE_MEDIA_TYPE_VIDEO)
+ media_attr = "video";
+ else
+ g_assert_not_reached ();
+ break;
+ }
+
+ desc_node = wocky_node_add_child_ns (content_node, "description", xmlns);
+
+ if (media_attr != NULL)
+ wocky_node_set_attribute (desc_node, "media", media_attr);
+
+ return desc_node;
+}
+
+static void
+produce_hdrext (gpointer data, gpointer user_data)
+{
+ WockyJingleRtpHeaderExtension *hdrext = data;
+ WockyNode *desc_node = user_data;
+ WockyNode *hdrext_node;
+ gchar buf[16];
+
+ hdrext_node = wocky_node_add_child (desc_node, "rtp-hdrext");
+
+ /* id: required */
+ sprintf (buf, "%d", hdrext->id);
+ wocky_node_set_attribute (hdrext_node, "id", buf);
+ wocky_node_set_attribute (hdrext_node, "uri", hdrext->uri);
+
+ if (hdrext->senders == WOCKY_JINGLE_CONTENT_SENDERS_INITIATOR)
+ wocky_node_set_attribute (hdrext_node, "senders", "initiator");
+ else if (hdrext->senders == WOCKY_JINGLE_CONTENT_SENDERS_RESPONDER)
+ wocky_node_set_attribute (hdrext_node, "senders", "responder");
+
+ wocky_node_set_attribute (hdrext_node, "xmlns", WOCKY_XMPP_NS_JINGLE_RTP_HDREXT);
+}
+
+static void
+produce_description (WockyJingleContent *content, WockyNode *content_node)
+{
+ WockyJingleMediaRtp *self = WOCKY_JINGLE_MEDIA_RTP (content);
+ WockyJingleMediaRtpPrivate *priv = self->priv;
+ GList *li;
+ WockyJingleDialect dialect = wocky_jingle_session_get_dialect (content->session);
+ WockyNode *desc_node;
+
+ if (wocky_jingle_session_peer_has_cap (content->session, WOCKY_XMPP_NS_JINGLE_RTCP_FB))
+ priv->has_rtcp_fb = TRUE;
+
+ if (wocky_jingle_session_peer_has_cap (content->session, WOCKY_XMPP_NS_JINGLE_RTP_HDREXT))
+ priv->has_rtp_hdrext = TRUE;
+
+ desc_node = produce_description_node (dialect, priv->media_type,
+ content_node);
+
+ /* For GTalk3 the description is added by the session */
+ if (desc_node == NULL)
+ desc_node = content_node;
+
+ /* If we're only updating our codec parameters, only generate payload-types
+ * for those.
+ */
+ if (priv->local_codec_updates != NULL)
+ li = priv->local_codec_updates;
+ else
+ li = priv->local_media_description->codecs;
+
+ for (; li != NULL; li = li->next)
+ produce_payload_type (content, desc_node, priv->media_type, li->data,
+ dialect);
+
+ if (priv->has_rtp_hdrext && priv->local_media_description->hdrexts)
+ g_list_foreach (priv->local_media_description->hdrexts, produce_hdrext,
+ desc_node);
+
+ if (priv->has_rtcp_fb)
+ {
+ g_list_foreach (priv->local_media_description->feedback_msgs,
+ (GFunc) produce_rtcp_fb, desc_node);
+ produce_rtcp_fb_trr_int (desc_node,
+ priv->local_media_description->trr_int);
+ }
+}
+
+/**
+ * string_string_maps_equal:
+ *
+ * Returns: TRUE iff @a and @b contain exactly the same keys and values when
+ * compared as strings.
+ */
+static gboolean
+string_string_maps_equal (GHashTable *a,
+ GHashTable *b)
+{
+ GHashTableIter iter;
+ gpointer a_key, a_value, b_value;
+
+ if (g_hash_table_size (a) != g_hash_table_size (b))
+ return FALSE;
+
+ g_hash_table_iter_init (&iter, a);
+
+ while (g_hash_table_iter_next (&iter, &a_key, &a_value))
+ {
+ if (!g_hash_table_lookup_extended (b, a_key, NULL, &b_value))
+ return FALSE;
+
+ if (wocky_strdiff (a_value, b_value))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * compare_codecs:
+ * @old: previous local codecs
+ * @new: new local codecs supplied by streaming implementation
+ * @changed: location at which to store the changed codecs
+ * @error: location at which to store an error if the update was invalid
+ *
+ * Returns: %TRUE if the update made sense, %FALSE with @error set otherwise
+ */
+gboolean
+jingle_media_rtp_compare_codecs (GList *old,
+ GList *new,
+ GList **changed,
+ GError **e)
+{
+ gboolean ret = FALSE;
+ GHashTable *old_table = build_codec_table (old);
+ GList *l;
+ WockyJingleCodec *old_c, *new_c;
+
+ g_assert (changed != NULL && *changed == NULL);
+
+ for (l = new; l != NULL; l = l->next)
+ {
+ new_c = l->data;
+ old_c = g_hash_table_lookup (old_table, GUINT_TO_POINTER (
+ (guint) new_c->id));
+
+ if (!codec_update_coherent (old_c, new_c, e))
+ goto out;
+
+ if (!string_string_maps_equal (old_c->params, new_c->params))
+ *changed = g_list_prepend (*changed, new_c);
+ }
+
+ ret = TRUE;
+
+out:
+ if (!ret)
+ {
+ g_list_free (*changed);
+ *changed = NULL;
+ }
+
+ g_hash_table_unref (old_table);
+ return ret;
+}
+
+/**
+ * jingle_media_rtp_set_local_media_description:
+ * @self: a content in an RTP session
+ * @md: (transfer full): new media description for this content
+ * @ready: whether the codecs can regarded as ready to sent from now on
+ * @error: used to return a %WOCKY_XMPP_ERROR if the codec update is illegal.
+ *
+ * Sets or updates the media description (codecs, feedback messages, etc) for
+ * @self.
+ *
+ * Returns: %TRUE if no description was previously set, or if the update is
+ * compatible with the existing description; %FALSE if the update is illegal
+ * (due to adding previously-unknown codecs or renaming an existing codec, for
+ * example)
+ */
+gboolean
+jingle_media_rtp_set_local_media_description (WockyJingleMediaRtp *self,
+ WockyJingleMediaDescription *md,
+ gboolean ready,
+ GError **error)
+{
+ WockyJingleMediaRtpPrivate *priv = self->priv;
+
+ DEBUG ("setting new local media description");
+
+ if (priv->local_media_description != NULL)
+ {
+ GList *changed = NULL;
+ GError *err = NULL;
+
+ g_assert (priv->local_codec_updates == NULL);
+
+ if (!jingle_media_rtp_compare_codecs (
+ priv->local_media_description->codecs,
+ md->codecs, &changed, &err))
+ {
+ DEBUG ("codec update was illegal: %s", err->message);
+ wocky_jingle_media_description_free (md);
+ g_propagate_error (error, err);
+ return FALSE;
+ }
+
+ if (changed == NULL)
+ {
+ DEBUG ("codec update changed nothing!");
+ wocky_jingle_media_description_free (md);
+ goto out;
+ }
+
+ DEBUG ("%u codecs changed", g_list_length (changed));
+ priv->local_codec_updates = changed;
+
+ wocky_jingle_media_description_free (priv->local_media_description);
+ }
+
+ priv->local_media_description = md;
+
+ /* Codecs have changed, sending a fresh description might be necessary */
+ wocky_jingle_content_maybe_send_description (WOCKY_JINGLE_CONTENT (self));
+
+ /* Update done if any, free the changed codecs if any */
+ g_list_free (priv->local_codec_updates);
+ priv->local_codec_updates = NULL;
+
+out:
+ if (ready)
+ _wocky_jingle_content_set_media_ready (WOCKY_JINGLE_CONTENT (self));
+
+ return TRUE;
+}
+
+void
+jingle_media_rtp_register (WockyJingleFactory *factory)
+{
+ /* Current (v0.25) Jingle draft URI */
+ wocky_jingle_factory_register_content_type (factory,
+ WOCKY_XMPP_NS_JINGLE_RTP, WOCKY_TYPE_JINGLE_MEDIA_RTP);
+
+ /* Old Jingle audio/video namespaces */
+ wocky_jingle_factory_register_content_type (factory,
+ WOCKY_XMPP_NS_JINGLE_DESCRIPTION_AUDIO,
+ WOCKY_TYPE_JINGLE_MEDIA_RTP);
+
+ wocky_jingle_factory_register_content_type (factory,
+ WOCKY_XMPP_NS_JINGLE_DESCRIPTION_VIDEO,
+ WOCKY_TYPE_JINGLE_MEDIA_RTP);
+
+ /* GTalk audio call namespace */
+ wocky_jingle_factory_register_content_type (factory,
+ WOCKY_XMPP_NS_GOOGLE_SESSION_PHONE,
+ WOCKY_TYPE_JINGLE_MEDIA_RTP);
+
+ /* GTalk video call namespace */
+ wocky_jingle_factory_register_content_type (factory,
+ WOCKY_XMPP_NS_GOOGLE_SESSION_VIDEO,
+ WOCKY_TYPE_JINGLE_MEDIA_RTP);
+}
+
+/* We can't get remote media description when they're signalled, because
+ * the signal is emitted immediately upon JingleContent creation,
+ * and parsing, which is before a corresponding MediaStream is
+ * created. */
+
+/**
+ * wocky_jingle_media_rtp_get_remote_media_description:
+ * @self : the RTP content
+ *
+ * Gets the current remote media description, if known. The
+ * #WockyJingleMediaRtp:remote-media-description signal is emitted when this
+ * value changes.
+ *
+ * Returns: (transfer none): the current remote media description, which may be
+ * %NULL for outgoing calls until it is first received.
+ */
+WockyJingleMediaDescription *
+wocky_jingle_media_rtp_get_remote_media_description (
+ WockyJingleMediaRtp *self)
+{
+ WockyJingleMediaRtpPrivate *priv = self->priv;
+
+ return priv->remote_media_description;
+}
+
+
+/**
+ * WockyJingleMediaDescription:
+ * @codecs: a list of #WockyJingleCodec<!-- -->s, allocated with
+ * jingle_media_rtp_codec_new()
+ * @hdrexts: a list of #WockyJingleRtpHeaderExtension<!-- -->s, allocated with
+ * wocky_jingle_rtp_header_extension_new()
+ * @trr_int: number of milliseconds between regular RTCP reports
+ * @feedback_msgs: a list of #WockyJingleFeedbackMessage<!-- -->s, allocated
+ * with wocky_jingle_feedback_message_new()
+ *
+ * Media description for a #WockyJingleMediaRtp content.
+ */
+
+
+/**
+ * wocky_jingle_media_description_new:
+ *
+ * Allocates a new media description. You should fill in all the fields yourself.
+ *
+ * Returns: a new, empty, media description
+ */
+WockyJingleMediaDescription *
+wocky_jingle_media_description_new (void)
+{
+ WockyJingleMediaDescription *md = g_slice_new0 (WockyJingleMediaDescription);
+
+ md->trr_int = G_MAXUINT;
+
+ return md;
+}
+
+void
+wocky_jingle_media_description_free (WockyJingleMediaDescription *md)
+{
+ jingle_media_rtp_free_codecs (md->codecs);
+
+ while (md->hdrexts != NULL)
+ {
+ wocky_jingle_rtp_header_extension_free (md->hdrexts->data);
+ md->hdrexts = g_list_delete_link (md->hdrexts, md->hdrexts);
+ }
+
+ g_slice_free (WockyJingleMediaDescription, md);
+}
+
+
+/**
+ * wocky_jingle_media_description_copy:
+ * @md: a media description
+ *
+ * Performs a deep copy of a media description.
+ *
+ * Returns: (transfer full): a deep copy of @md
+ */
+WockyJingleMediaDescription *
+wocky_jingle_media_description_copy (WockyJingleMediaDescription *md)
+{
+ WockyJingleMediaDescription *newmd = g_slice_new0 (WockyJingleMediaDescription);
+ GList *li;
+
+ newmd->codecs = jingle_media_rtp_copy_codecs (md->codecs);
+ newmd->feedback_msgs = wocky_jingle_feedback_message_list_copy (md->feedback_msgs);
+ newmd->trr_int = md->trr_int;
+
+ for (li = md->hdrexts; li; li = li->next)
+ {
+ WockyJingleRtpHeaderExtension *h = li->data;
+
+ newmd->hdrexts = g_list_append (newmd->hdrexts,
+ wocky_jingle_rtp_header_extension_new (h->id, h->senders, h->uri));
+ }
+
+ return newmd;
+}
+
+WockyJingleRtpHeaderExtension *
+wocky_jingle_rtp_header_extension_new (guint id, WockyJingleContentSenders senders,
+ const gchar *uri)
+{
+ WockyJingleRtpHeaderExtension *hdrext = g_slice_new (WockyJingleRtpHeaderExtension);
+
+ hdrext->id = id;
+ hdrext->senders = senders;
+ hdrext->uri = g_strdup (uri);
+
+ return hdrext;
+}
+
+void
+wocky_jingle_rtp_header_extension_free (WockyJingleRtpHeaderExtension *hdrext)
+{
+ g_free (hdrext->uri);
+ g_slice_free (WockyJingleRtpHeaderExtension, hdrext);
+}
+
+WockyJingleFeedbackMessage *
+wocky_jingle_feedback_message_new (const gchar *type, const gchar *subtype)
+{
+ WockyJingleFeedbackMessage *fb = g_slice_new0 (WockyJingleFeedbackMessage);
+
+ fb->type = g_strdup (type);
+ fb->subtype = g_strdup (subtype);
+
+ return fb;
+}
+
+void
+wocky_jingle_feedback_message_free (WockyJingleFeedbackMessage *fb)
+{
+ g_free (fb->type);
+ g_free (fb->subtype);
+ g_slice_free (WockyJingleFeedbackMessage, fb);
+}
+
+
+static gint
+wocky_jingle_feedback_message_compare (const WockyJingleFeedbackMessage *fb1,
+ const WockyJingleFeedbackMessage *fb2)
+{
+ if (!g_ascii_strcasecmp (fb1->type, fb2->type) &&
+ !g_ascii_strcasecmp (fb1->subtype, fb2->subtype))
+ return 0;
+ else
+ return 1;
+}
+
+/**
+ * wocky_jingle_media_description_simplify:
+ * @md: a description to simplify
+ *
+ * Removes duplicated Feedback message and put them in the global structure
+ *
+ * This function will iterate over every codec in a description and look for
+ * feedback messages that are exactly the same in every codec and will instead
+ * put the in the list in the description and remove them from the childs.
+ * This limits the amount of duplication in the resulting XML.
+ */
+void
+wocky_jingle_media_description_simplify (WockyJingleMediaDescription *md)
+{
+ GList *item;
+ guint trr_int = 0;
+ gboolean trr_int_all_same = TRUE;
+ gboolean init = FALSE;
+ GList *identical_fbs = NULL;
+
+ for (item = md->codecs; item; item = item->next)
+ {
+ WockyJingleCodec *c = item->data;
+
+ if (!init)
+ {
+ /* For the first codec, it stores the trr_int and the list
+ * of feedback messages */
+ trr_int = c->trr_int;
+ identical_fbs = g_list_copy (c->feedback_msgs);
+ init = TRUE;
+ }
+ else
+ {
+ GList *item2;
+
+ /* For every subsequent codec, we check if the trr_int is the same */
+
+ if (trr_int != c->trr_int)
+ trr_int_all_same = FALSE;
+
+ /* We also intersect the remembered list of feedback messages with
+ * the list for that codec and remove any feedback message that isn't
+ * in both
+ */
+
+ for (item2 = identical_fbs; item2;)
+ {
+ WockyJingleFeedbackMessage *fb = identical_fbs->data;
+ GList *next = item2->next;
+
+ if (!g_list_find_custom (c->feedback_msgs, fb,
+ (GCompareFunc) wocky_jingle_feedback_message_compare))
+ identical_fbs = g_list_delete_link (identical_fbs, item2);
+
+ item2 = next;
+ }
+
+ /* If the trr_int is not the same everywhere and there are not common
+ * feedback messages, then stop
+ */
+ if (!trr_int_all_same && identical_fbs == NULL)
+ break;
+ }
+ }
+
+ if (trr_int_all_same && trr_int == G_MAXUINT)
+ trr_int_all_same = FALSE;
+
+ /* if the trr_int is the same everywhere, lets set it globally */
+ if (trr_int_all_same)
+ md->trr_int = trr_int;
+
+ /* If there are feedback messages that are in every codec, put a copy of them
+ * in the global structure
+ */
+ if (identical_fbs)
+ {
+ md->feedback_msgs = wocky_jingle_feedback_message_list_copy (identical_fbs);
+ g_list_free (identical_fbs);
+ }
+
+ if (trr_int_all_same || md->feedback_msgs != NULL)
+ for (item = md->codecs; item; item = item->next)
+ {
+ WockyJingleCodec *c = item->data;
+ GList *item2;
+
+ /* If the trr_int is the same everywhere, lets put the default on
+ * each codec, we have it in the main structure
+ */
+ if (trr_int_all_same)
+ c->trr_int = G_MAXUINT;
+
+ /* Find the feedback messages that were put in the main structure and
+ * remove them from each codec
+ */
+ for (item2 = md->feedback_msgs; item2; item2 = item2->next)
+ {
+ GList *duplicated;
+ WockyJingleFeedbackMessage *fb = item2->data;
+
+ while ((duplicated = g_list_find_custom (c->feedback_msgs, fb,
+ (GCompareFunc) wocky_jingle_feedback_message_compare)) != NULL)
+ {
+ wocky_jingle_feedback_message_free (duplicated->data);
+ c->feedback_msgs = g_list_delete_link (c->feedback_msgs,
+ duplicated);
+ }
+ }
+ }
+}
diff --git a/wocky/wocky-jingle-media-rtp.h b/wocky/wocky-jingle-media-rtp.h
new file mode 100644
index 0000000..6ea7008
--- /dev/null
+++ b/wocky/wocky-jingle-media-rtp.h
@@ -0,0 +1,129 @@
+/*
+ * wocky-jingle-media-rtp.h - Header for WockyJingleMediaRtp
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#if !defined (WOCKY_H_INSIDE) && !defined (WOCKY_COMPILATION)
+# error "Only <wocky/wocky.h> can be included directly."
+#endif
+
+#ifndef __JINGLE_MEDIA_RTP_H__
+#define __JINGLE_MEDIA_RTP_H__
+
+#include <glib-object.h>
+
+#include "wocky-jingle-content.h"
+#include "wocky-jingle-types.h"
+
+G_BEGIN_DECLS
+
+typedef struct _WockyJingleMediaRtpClass WockyJingleMediaRtpClass;
+
+GType wocky_jingle_media_rtp_get_type (void);
+
+/* TYPE MACROS */
+#define WOCKY_TYPE_JINGLE_MEDIA_RTP \
+ (wocky_jingle_media_rtp_get_type ())
+#define WOCKY_JINGLE_MEDIA_RTP(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), WOCKY_TYPE_JINGLE_MEDIA_RTP, \
+ WockyJingleMediaRtp))
+#define WOCKY_JINGLE_MEDIA_RTP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), WOCKY_TYPE_JINGLE_MEDIA_RTP, \
+ WockyJingleMediaRtpClass))
+#define WOCKY_IS_JINGLE_MEDIA_RTP(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), WOCKY_TYPE_JINGLE_MEDIA_RTP))
+#define WOCKY_IS_JINGLE_MEDIA_RTP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), WOCKY_TYPE_JINGLE_MEDIA_RTP))
+#define WOCKY_JINGLE_MEDIA_RTP_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), WOCKY_TYPE_JINGLE_MEDIA_RTP, \
+ WockyJingleMediaRtpClass))
+
+struct _WockyJingleMediaRtpClass {
+ WockyJingleContentClass parent_class;
+};
+
+typedef struct _WockyJingleMediaRtpPrivate WockyJingleMediaRtpPrivate;
+
+struct _WockyJingleMediaRtp {
+ WockyJingleContent parent;
+ WockyJingleMediaRtpPrivate *priv;
+};
+
+typedef struct {
+ guint id;
+ gchar *name;
+ guint clockrate;
+ guint channels;
+ GHashTable *params;
+ guint trr_int;
+ GList *feedback_msgs;
+} WockyJingleCodec;
+
+typedef struct {
+ gchar *type;
+ gchar *subtype;
+} WockyJingleFeedbackMessage;
+
+typedef struct {
+ guint id;
+ WockyJingleContentSenders senders;
+ gchar *uri;
+} WockyJingleRtpHeaderExtension;
+
+typedef struct {
+ GList *codecs;
+ GList *hdrexts;
+ guint trr_int;
+ GList *feedback_msgs;
+} WockyJingleMediaDescription;
+
+void jingle_media_rtp_register (WockyJingleFactory *factory);
+gboolean jingle_media_rtp_set_local_media_description (
+ WockyJingleMediaRtp *self, WockyJingleMediaDescription *md, gboolean ready,
+ GError **error);
+WockyJingleMediaDescription *wocky_jingle_media_rtp_get_remote_media_description (
+ WockyJingleMediaRtp *self);
+
+WockyJingleCodec * jingle_media_rtp_codec_new (guint id, const gchar *name,
+ guint clockrate, guint channels, GHashTable *params);
+void jingle_media_rtp_codec_free (WockyJingleCodec *p);
+void jingle_media_rtp_free_codecs (GList *codecs);
+GList * jingle_media_rtp_copy_codecs (GList *codecs);
+
+gboolean jingle_media_rtp_compare_codecs (GList *old,
+ GList *new,
+ GList **changed,
+ GError **e);
+
+WockyJingleMediaDescription *wocky_jingle_media_description_new (void);
+void wocky_jingle_media_description_free (WockyJingleMediaDescription *md);
+WockyJingleMediaDescription *wocky_jingle_media_description_copy (
+ WockyJingleMediaDescription *md);
+
+WockyJingleRtpHeaderExtension *wocky_jingle_rtp_header_extension_new (guint id,
+ WockyJingleContentSenders senders, const gchar *uri);
+void wocky_jingle_rtp_header_extension_free (WockyJingleRtpHeaderExtension *hdrext);
+
+
+WockyJingleFeedbackMessage *wocky_jingle_feedback_message_new (const gchar *type,
+ const gchar *subtype);
+void wocky_jingle_feedback_message_free (WockyJingleFeedbackMessage *fb);
+void wocky_jingle_media_description_simplify (WockyJingleMediaDescription *md);
+
+G_END_DECLS
+
+#endif /* __JINGLE_MEDIA_RTP_H__ */
+
diff --git a/wocky/wocky-jingle-session.c b/wocky/wocky-jingle-session.c
new file mode 100644
index 0000000..3caa975
--- /dev/null
+++ b/wocky/wocky-jingle-session.c
@@ -0,0 +1,2515 @@
+/*
+ * wocky-jingle-session.c - Source for WockyJingleSession
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "wocky-jingle-session.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#define WOCKY_DEBUG_FLAG WOCKY_DEBUG_JINGLE
+
+#include "wocky-debug-internal.h"
+#include "wocky-signals-marshal.h"
+#include "wocky-enumtypes.h"
+#include "wocky-jingle-content.h"
+#include "wocky-jingle-factory.h"
+/* FIXME: the RTP-specific bits of this file should be separated from the
+ * generic Jingle code.
+ */
+#include "wocky-jingle-media-rtp.h"
+#include "wocky-namespaces.h"
+#include "wocky-resource-contact.h"
+#include "wocky-utils.h"
+
+G_DEFINE_TYPE(WockyJingleSession, wocky_jingle_session, G_TYPE_OBJECT);
+
+/* signal enum */
+enum
+{
+ NEW_CONTENT,
+ REMOTE_STATE_CHANGED,
+ TERMINATED,
+ CONTENT_REJECTED,
+ QUERY_CAP,
+ ABOUT_TO_INITIATE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum
+{
+ PROP_JINGLE_FACTORY = 1,
+ PROP_PORTER,
+ PROP_SESSION_ID,
+ PROP_PEER_CONTACT,
+ PROP_LOCAL_INITIATOR,
+ PROP_STATE,
+ PROP_DIALECT,
+ PROP_LOCAL_HOLD,
+ PROP_REMOTE_HOLD,
+ PROP_REMOTE_RINGING,
+ LAST_PROPERTY
+};
+
+struct _WockyJingleSessionPrivate
+{
+ /* Borrowed; the factory owns us. */
+ WockyJingleFactory *jingle_factory;
+ WockyPorter *porter;
+
+ WockyContact *peer_contact;
+ /* Borrowed from peer_contact if it's a WockyResourceContact. */
+ const gchar *peer_resource;
+ gchar *peer_jid;
+ /* Either borrowed from 'porter' or equal to peer_jid. */
+ const gchar *initiator;
+ gboolean local_initiator;
+
+ /* WockyJingleContent objects keyed by content name.
+ * Table owns references to these objects. */
+ GHashTable *initiator_contents;
+ GHashTable *responder_contents;
+
+ WockyJingleDialect dialect;
+ WockyJingleState state;
+ gchar *sid;
+
+ gboolean locally_accepted;
+ gboolean locally_terminated;
+
+ gboolean local_hold;
+
+ gboolean remote_hold;
+ gboolean remote_ringing;
+
+ gboolean dispose_has_run;
+};
+
+typedef struct {
+ WockyJingleState state;
+ WockyJingleAction *actions;
+} WockyJingleStateActions;
+
+/* gcc should be able to figure this out from the table below, but.. */
+#define MAX_ACTIONS_PER_STATE 12
+
+/* NB: WOCKY_JINGLE_ACTION_UNKNOWN is used as a terminator here. */
+static WockyJingleAction allowed_actions[WOCKY_N_JINGLE_STATES][MAX_ACTIONS_PER_STATE] = {
+ /* WOCKY_JINGLE_STATE_PENDING_CREATED */
+ { WOCKY_JINGLE_ACTION_SESSION_INITIATE, WOCKY_JINGLE_ACTION_UNKNOWN },
+ /* WOCKY_JINGLE_STATE_PENDING_INITIATE_SENT */
+ { WOCKY_JINGLE_ACTION_SESSION_TERMINATE, WOCKY_JINGLE_ACTION_SESSION_ACCEPT,
+ WOCKY_JINGLE_ACTION_TRANSPORT_ACCEPT, /* required for GTalk4 */
+ WOCKY_JINGLE_ACTION_DESCRIPTION_INFO, WOCKY_JINGLE_ACTION_SESSION_INFO,
+ WOCKY_JINGLE_ACTION_TRANSPORT_INFO, WOCKY_JINGLE_ACTION_INFO,
+ WOCKY_JINGLE_ACTION_UNKNOWN },
+ /* WOCKY_JINGLE_STATE_PENDING_INITIATED */
+ { WOCKY_JINGLE_ACTION_SESSION_ACCEPT, WOCKY_JINGLE_ACTION_SESSION_TERMINATE,
+ WOCKY_JINGLE_ACTION_TRANSPORT_INFO, WOCKY_JINGLE_ACTION_CONTENT_REJECT,
+ WOCKY_JINGLE_ACTION_CONTENT_MODIFY, WOCKY_JINGLE_ACTION_CONTENT_ACCEPT,
+ WOCKY_JINGLE_ACTION_CONTENT_REMOVE, WOCKY_JINGLE_ACTION_DESCRIPTION_INFO,
+ WOCKY_JINGLE_ACTION_TRANSPORT_ACCEPT, WOCKY_JINGLE_ACTION_SESSION_INFO,
+ WOCKY_JINGLE_ACTION_INFO,
+ WOCKY_JINGLE_ACTION_UNKNOWN },
+ /* WOCKY_JINGLE_STATE_PENDING_ACCEPT_SENT */
+ { WOCKY_JINGLE_ACTION_TRANSPORT_INFO, WOCKY_JINGLE_ACTION_DESCRIPTION_INFO,
+ WOCKY_JINGLE_ACTION_SESSION_TERMINATE, WOCKY_JINGLE_ACTION_SESSION_INFO,
+ WOCKY_JINGLE_ACTION_INFO,
+ WOCKY_JINGLE_ACTION_UNKNOWN },
+ /* WOCKY_JINGLE_STATE_ACTIVE */
+ { WOCKY_JINGLE_ACTION_CONTENT_MODIFY, WOCKY_JINGLE_ACTION_CONTENT_ADD,
+ WOCKY_JINGLE_ACTION_CONTENT_REMOVE, WOCKY_JINGLE_ACTION_CONTENT_REPLACE,
+ WOCKY_JINGLE_ACTION_CONTENT_ACCEPT, WOCKY_JINGLE_ACTION_CONTENT_REJECT,
+ WOCKY_JINGLE_ACTION_SESSION_INFO, WOCKY_JINGLE_ACTION_TRANSPORT_INFO,
+ WOCKY_JINGLE_ACTION_DESCRIPTION_INFO, WOCKY_JINGLE_ACTION_INFO,
+ WOCKY_JINGLE_ACTION_SESSION_TERMINATE, WOCKY_JINGLE_ACTION_UNKNOWN },
+ /* WOCKY_JINGLE_STATE_ENDED */
+ { WOCKY_JINGLE_ACTION_UNKNOWN }
+};
+
+gboolean
+wocky_jingle_session_defines_action (WockyJingleSession *sess,
+ WockyJingleAction a)
+{
+ WockyJingleDialect d = sess->priv->dialect;
+
+ if (a == WOCKY_JINGLE_ACTION_UNKNOWN)
+ return FALSE;
+
+ switch (d)
+ {
+ case WOCKY_JINGLE_DIALECT_V032:
+ return TRUE;
+ case WOCKY_JINGLE_DIALECT_V015:
+ return (a != WOCKY_JINGLE_ACTION_DESCRIPTION_INFO &&
+ a != WOCKY_JINGLE_ACTION_SESSION_INFO);
+ case WOCKY_JINGLE_DIALECT_GTALK4:
+ if (a == WOCKY_JINGLE_ACTION_TRANSPORT_ACCEPT ||
+ a == WOCKY_JINGLE_ACTION_INFO )
+ return TRUE;
+ case WOCKY_JINGLE_DIALECT_GTALK3:
+ return (a == WOCKY_JINGLE_ACTION_SESSION_ACCEPT ||
+ a == WOCKY_JINGLE_ACTION_SESSION_INITIATE ||
+ a == WOCKY_JINGLE_ACTION_SESSION_TERMINATE ||
+ a == WOCKY_JINGLE_ACTION_TRANSPORT_INFO ||
+ a == WOCKY_JINGLE_ACTION_INFO);
+ default:
+ return FALSE;
+ }
+}
+
+static void wocky_jingle_session_send_held (WockyJingleSession *sess);
+static void content_ready_cb (WockyJingleContent *c, gpointer user_data);
+static void content_removed_cb (WockyJingleContent *c, gpointer user_data);
+
+static void
+wocky_jingle_session_init (WockyJingleSession *obj)
+{
+ WockyJingleSessionPrivate *priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (obj, WOCKY_TYPE_JINGLE_SESSION,
+ WockyJingleSessionPrivate);
+ obj->priv = priv;
+
+ DEBUG ("Initializing the jingle session %p", obj);
+
+ priv->initiator_contents = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+ priv->responder_contents = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+
+ priv->state = WOCKY_JINGLE_STATE_PENDING_CREATED;
+ priv->locally_accepted = FALSE;
+ priv->locally_terminated = FALSE;
+ priv->dispose_has_run = FALSE;
+}
+
+static void
+dispose_content_hash (
+ WockyJingleSession *sess,
+ GHashTable **contents)
+{
+ GHashTableIter iter;
+ gpointer content;
+
+ g_hash_table_iter_init (&iter, *contents);
+ while (g_hash_table_iter_next (&iter, NULL, &content))
+ {
+ g_signal_handlers_disconnect_by_func (content, content_ready_cb, sess);
+ g_signal_handlers_disconnect_by_func (content, content_removed_cb, sess);
+ g_hash_table_iter_remove (&iter);
+ }
+
+ g_hash_table_unref (*contents);
+ *contents = NULL;
+}
+
+static void
+wocky_jingle_session_dispose (GObject *object)
+{
+ WockyJingleSession *sess = WOCKY_JINGLE_SESSION (object);
+ WockyJingleSessionPrivate *priv = sess->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ DEBUG ("called");
+ priv->dispose_has_run = TRUE;
+
+ g_assert ((priv->state == WOCKY_JINGLE_STATE_PENDING_CREATED) ||
+ (priv->state == WOCKY_JINGLE_STATE_ENDED));
+
+ dispose_content_hash (sess, &priv->initiator_contents);
+ dispose_content_hash (sess, &priv->responder_contents);
+
+ g_clear_object (&priv->peer_contact);
+ g_clear_object (&priv->porter);
+
+ g_free (priv->sid);
+ priv->sid = NULL;
+
+ g_free (priv->peer_jid);
+ priv->peer_jid = NULL;
+
+ if (G_OBJECT_CLASS (wocky_jingle_session_parent_class)->dispose)
+ G_OBJECT_CLASS (wocky_jingle_session_parent_class)->dispose (object);
+}
+
+static void
+wocky_jingle_session_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleSession *sess = WOCKY_JINGLE_SESSION (object);
+ WockyJingleSessionPrivate *priv = sess->priv;
+
+ switch (property_id) {
+ case PROP_JINGLE_FACTORY:
+ g_value_set_object (value, priv->jingle_factory);
+ break;
+ case PROP_PORTER:
+ g_value_set_object (value, priv->porter);
+ break;
+ case PROP_SESSION_ID:
+ g_value_set_string (value, priv->sid);
+ break;
+ case PROP_LOCAL_INITIATOR:
+ g_value_set_boolean (value, priv->local_initiator);
+ break;
+ case PROP_PEER_CONTACT:
+ g_value_set_object (value, priv->peer_contact);
+ break;
+ case PROP_STATE:
+ g_value_set_uint (value, priv->state);
+ break;
+ case PROP_DIALECT:
+ g_value_set_uint (value, priv->dialect);
+ break;
+ case PROP_LOCAL_HOLD:
+ g_value_set_boolean (value, priv->local_hold);
+ break;
+ case PROP_REMOTE_HOLD:
+ g_value_set_boolean (value, priv->remote_hold);
+ break;
+ case PROP_REMOTE_RINGING:
+ g_value_set_boolean (value, priv->remote_ringing);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_jingle_session_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleSession *sess = WOCKY_JINGLE_SESSION (object);
+ WockyJingleSessionPrivate *priv = sess->priv;
+
+ switch (property_id) {
+ case PROP_JINGLE_FACTORY:
+ priv->jingle_factory = g_value_get_object (value);
+ g_assert (priv->jingle_factory != NULL);
+ break;
+ case PROP_PORTER:
+ priv->porter = g_value_dup_object (value);
+ g_assert (priv->porter != NULL);
+ break;
+ case PROP_SESSION_ID:
+ g_free (priv->sid);
+ priv->sid = g_value_dup_string (value);
+ break;
+ case PROP_LOCAL_INITIATOR:
+ priv->local_initiator = g_value_get_boolean (value);
+ break;
+ case PROP_DIALECT:
+ priv->dialect = g_value_get_uint (value);
+ break;
+ case PROP_PEER_CONTACT:
+ priv->peer_contact = g_value_dup_object (value);
+ break;
+ case PROP_LOCAL_HOLD:
+ {
+ gboolean local_hold = g_value_get_boolean (value);
+
+ if (priv->local_hold != local_hold)
+ {
+ priv->local_hold = local_hold;
+
+ if (priv->state >= WOCKY_JINGLE_STATE_PENDING_INITIATED &&
+ priv->state < WOCKY_JINGLE_STATE_ENDED)
+ wocky_jingle_session_send_held (sess);
+
+ /* else, we'll send this in set_state when we move to PENDING_INITIATED or
+ * better.
+ */
+ }
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+wocky_jingle_session_constructed (GObject *object)
+{
+ void (*chain_up) (GObject *) =
+ G_OBJECT_CLASS (wocky_jingle_session_parent_class)->constructed;
+ WockyJingleSession *self = WOCKY_JINGLE_SESSION (object);
+ WockyJingleSessionPrivate *priv = self->priv;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ g_assert (priv->jingle_factory != NULL);
+ g_assert (priv->porter != NULL);
+ g_assert (priv->peer_contact != NULL);
+ g_assert (priv->sid != NULL);
+
+ priv->peer_jid = wocky_contact_dup_jid (priv->peer_contact);
+
+ if (priv->local_initiator)
+ priv->initiator = wocky_porter_get_full_jid (priv->porter);
+ else
+ priv->initiator = priv->peer_jid;
+
+ if (WOCKY_IS_RESOURCE_CONTACT (priv->peer_contact))
+ priv->peer_resource = wocky_resource_contact_get_resource (
+ WOCKY_RESOURCE_CONTACT (priv->peer_contact));
+}
+
+WockyJingleSession *
+wocky_jingle_session_new (
+ WockyJingleFactory *factory,
+ WockyPorter *porter,
+ const gchar *session_id,
+ gboolean local_initiator,
+ WockyContact *peer,
+ WockyJingleDialect dialect,
+ gboolean local_hold)
+{
+ return g_object_new (WOCKY_TYPE_JINGLE_SESSION,
+ "session-id", session_id,
+ "jingle-factory", factory,
+ "porter", porter,
+ "local-initiator", local_initiator,
+ "peer-contact", peer,
+ "dialect", dialect,
+ "local-hold", local_hold,
+ NULL);
+}
+
+static void
+wocky_jingle_session_class_init (WockyJingleSessionClass *cls)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (cls);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (cls, sizeof (WockyJingleSessionPrivate));
+
+ object_class->constructed = wocky_jingle_session_constructed;
+ object_class->get_property = wocky_jingle_session_get_property;
+ object_class->set_property = wocky_jingle_session_set_property;
+ object_class->dispose = wocky_jingle_session_dispose;
+
+ /* property definitions */
+ param_spec = g_param_spec_object ("jingle-factory",
+ "WockyJingleFactory object",
+ "The Jingle factory which created this session",
+ WOCKY_TYPE_JINGLE_FACTORY,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_JINGLE_FACTORY, param_spec);
+
+ param_spec = g_param_spec_object ("porter", "WockyPorter",
+ "The WockyPorter for the current connection",
+ WOCKY_TYPE_PORTER,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_PORTER, param_spec);
+
+ param_spec = g_param_spec_string ("session-id", "Session ID",
+ "A unique session identifier used throughout all communication.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SESSION_ID, param_spec);
+
+ param_spec = g_param_spec_boolean ("local-initiator", "Session initiator",
+ "Specifies if local end initiated the session.",
+ TRUE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_LOCAL_INITIATOR,
+ param_spec);
+
+ /**
+ * WockyJingleSession:peer-contact:
+ *
+ * The #WockyContact representing the other party in the session. Note that
+ * if this is a #WockyBareContact (as opposed to a #WockyResourceContact) the
+ * session is with the contact's bare JID.
+ */
+ param_spec = g_param_spec_object ("peer-contact", "Session peer",
+ "The WockyContact representing the other party in the session.",
+ WOCKY_TYPE_CONTACT,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_PEER_CONTACT, param_spec);
+
+ param_spec = g_param_spec_uint ("state", "Session state",
+ "The current state that the session is in.",
+ 0, G_MAXUINT32, WOCKY_JINGLE_STATE_PENDING_CREATED,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STATE, param_spec);
+
+ param_spec = g_param_spec_uint ("dialect", "Jingle dialect",
+ "Jingle dialect used for this session.",
+ 0, G_MAXUINT32, WOCKY_JINGLE_DIALECT_ERROR,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DIALECT, param_spec);
+
+ param_spec = g_param_spec_boolean ("local-hold", "Local hold",
+ "TRUE if we've placed the peer on hold", FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_LOCAL_HOLD, param_spec);
+
+ param_spec = g_param_spec_boolean ("remote-hold", "Remote hold",
+ "TRUE if the peer has placed us on hold", FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REMOTE_HOLD, param_spec);
+
+ param_spec = g_param_spec_boolean ("remote-ringing", "Remote ringing",
+ "TRUE if the peer's client is ringing", FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REMOTE_RINGING,
+ param_spec);
+
+ /* signal definitions */
+
+ signals[NEW_CONTENT] = g_signal_new ("new-content",
+ G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+ /**
+ * WockyJingleSession::terminated:
+ * @session: the session
+ * @locally_terminated: %TRUE if the session ended due to a call to
+ * wocky_jingle_session_terminate(); %FALSE if the peer ended the session.
+ * @reason: a #WockyJingleReason describing why the session terminated
+ * @text: a possibly-%NULL human-readable string describing why the session
+ * terminated
+ *
+ * Emitted when the session ends, just after #WockyJingleSession:state moves
+ * to #WOCKY_JINGLE_STATE_ENDED.
+ */
+ signals[TERMINATED] = g_signal_new ("terminated",
+ G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, _wocky_signals_marshal_VOID__BOOLEAN_UINT_STRING,
+ G_TYPE_NONE, 3, G_TYPE_BOOLEAN, G_TYPE_UINT, G_TYPE_STRING);
+
+ signals[REMOTE_STATE_CHANGED] = g_signal_new ("remote-state-changed",
+ G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[CONTENT_REJECTED] = g_signal_new ("content-rejected",
+ G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, _wocky_signals_marshal_VOID__OBJECT_UINT_STRING,
+ G_TYPE_NONE, 3, G_TYPE_OBJECT, G_TYPE_UINT, G_TYPE_STRING);
+
+ /*
+ * @contact: this call's peer (the artist commonly known as
+ * wocky_jingle_session_get_peer_contact())
+ * @cap: the XEP-0115 feature string the session is interested in.
+ *
+ * Emitted when the session wants to check whether the peer has a particular
+ * capability. The handler should return %TRUE if @contact has @cap.
+ */
+ signals[QUERY_CAP] = g_signal_new ("query-cap",
+ G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST,
+ 0, g_signal_accumulator_first_wins, NULL,
+ _wocky_signals_marshal_BOOLEAN__OBJECT_STRING,
+ G_TYPE_BOOLEAN, 2, WOCKY_TYPE_CONTACT, G_TYPE_STRING);
+
+ signals[ABOUT_TO_INITIATE] = g_signal_new ("about-to-initiate",
+ G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+typedef void (*HandlerFunc)(WockyJingleSession *sess,
+ WockyNode *node, GError **error);
+typedef void (*ContentHandlerFunc)(WockyJingleSession *sess,
+ WockyJingleContent *c, WockyNode *content_node, gpointer user_data,
+ GError **error);
+
+static gboolean
+extract_reason (WockyNode *node, WockyJingleReason *reason, gchar **message)
+{
+ WockyJingleReason _reason = WOCKY_JINGLE_REASON_UNKNOWN;
+ WockyNode *child;
+ WockyNodeIter iter;
+
+ g_return_val_if_fail (node != NULL, FALSE);
+
+ if (message != NULL)
+ *message = g_strdup (wocky_node_get_content_from_child (node, "text"));
+
+ wocky_node_iter_init (&iter, node, NULL, NULL);
+
+ while (wocky_node_iter_next (&iter, &child))
+ {
+ if (wocky_enum_from_nick (
+ wocky_jingle_reason_get_type (), child->name, (gint *) &_reason))
+ {
+ if (reason != NULL)
+ *reason = _reason;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static WockyJingleAction
+parse_action (const gchar *txt)
+{
+ if (txt == NULL)
+ return WOCKY_JINGLE_ACTION_UNKNOWN;
+
+ /* synonyms, best deal with them right now */
+ if (!wocky_strdiff (txt, "initiate") ||
+ !wocky_strdiff (txt, "session-initiate"))
+ return WOCKY_JINGLE_ACTION_SESSION_INITIATE;
+ else if (!wocky_strdiff (txt, "terminate") ||
+ !wocky_strdiff (txt, "session-terminate") ||
+ !wocky_strdiff (txt, "reject"))
+ return WOCKY_JINGLE_ACTION_SESSION_TERMINATE;
+ else if (!wocky_strdiff (txt, "accept") ||
+ !wocky_strdiff (txt, "session-accept"))
+ return WOCKY_JINGLE_ACTION_SESSION_ACCEPT;
+ else if (!wocky_strdiff (txt, "candidates") ||
+ !wocky_strdiff (txt, "transport-info"))
+ return WOCKY_JINGLE_ACTION_TRANSPORT_INFO;
+ else if (!wocky_strdiff (txt, "content-accept"))
+ return WOCKY_JINGLE_ACTION_CONTENT_ACCEPT;
+ else if (!wocky_strdiff (txt, "content-add"))
+ return WOCKY_JINGLE_ACTION_CONTENT_ADD;
+ else if (!wocky_strdiff (txt, "content-modify"))
+ return WOCKY_JINGLE_ACTION_CONTENT_MODIFY;
+ else if (!wocky_strdiff (txt, "content-replace"))
+ return WOCKY_JINGLE_ACTION_CONTENT_REPLACE;
+ else if (!wocky_strdiff (txt, "content-reject"))
+ return WOCKY_JINGLE_ACTION_CONTENT_REJECT;
+ else if (!wocky_strdiff (txt, "content-remove"))
+ return WOCKY_JINGLE_ACTION_CONTENT_REMOVE;
+ else if (!wocky_strdiff (txt, "session-info"))
+ return WOCKY_JINGLE_ACTION_SESSION_INFO;
+ else if (!wocky_strdiff (txt, "transport-accept"))
+ return WOCKY_JINGLE_ACTION_TRANSPORT_ACCEPT;
+ else if (!wocky_strdiff (txt, "description-info"))
+ return WOCKY_JINGLE_ACTION_DESCRIPTION_INFO;
+ else if (!wocky_strdiff (txt, "info"))
+ return WOCKY_JINGLE_ACTION_INFO;
+
+ return WOCKY_JINGLE_ACTION_UNKNOWN;
+}
+
+static const gchar *
+produce_action (WockyJingleAction action, WockyJingleDialect dialect)
+{
+ gboolean gmode = (dialect == WOCKY_JINGLE_DIALECT_GTALK3) ||
+ (dialect == WOCKY_JINGLE_DIALECT_GTALK4);
+
+ g_return_val_if_fail (action != WOCKY_JINGLE_ACTION_UNKNOWN, NULL);
+
+ switch (action) {
+ case WOCKY_JINGLE_ACTION_SESSION_INITIATE:
+ return (gmode) ? "initiate" : "session-initiate";
+ case WOCKY_JINGLE_ACTION_SESSION_TERMINATE:
+ return (gmode) ? "terminate" : "session-terminate";
+ case WOCKY_JINGLE_ACTION_SESSION_ACCEPT:
+ return (gmode) ? "accept" : "session-accept";
+ case WOCKY_JINGLE_ACTION_TRANSPORT_INFO:
+ return (dialect == WOCKY_JINGLE_DIALECT_GTALK3) ?
+ "candidates" : "transport-info";
+ case WOCKY_JINGLE_ACTION_CONTENT_ACCEPT:
+ return "content-accept";
+ case WOCKY_JINGLE_ACTION_CONTENT_ADD:
+ return "content-add";
+ case WOCKY_JINGLE_ACTION_CONTENT_MODIFY:
+ return "content-modify";
+ case WOCKY_JINGLE_ACTION_CONTENT_REMOVE:
+ return "content-remove";
+ case WOCKY_JINGLE_ACTION_CONTENT_REPLACE:
+ return "content-replace";
+ case WOCKY_JINGLE_ACTION_CONTENT_REJECT:
+ return "content-reject";
+ case WOCKY_JINGLE_ACTION_SESSION_INFO:
+ return "session-info";
+ case WOCKY_JINGLE_ACTION_TRANSPORT_ACCEPT:
+ return "transport-accept";
+ case WOCKY_JINGLE_ACTION_DESCRIPTION_INFO:
+ return "description-info";
+ case WOCKY_JINGLE_ACTION_INFO:
+ return "info";
+ default:
+ /* only reached if g_return_val_if_fail is disabled */
+ DEBUG ("unknown action %u", action);
+ return NULL;
+ }
+}
+
+static gboolean
+action_is_allowed (WockyJingleAction action, WockyJingleState state)
+{
+ guint i;
+
+ for (i = 0; allowed_actions[state][i] != WOCKY_JINGLE_ACTION_UNKNOWN; i++)
+ {
+ if (allowed_actions[state][i] == action)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void wocky_jingle_session_send_rtp_info (WockyJingleSession *sess,
+ const gchar *name);
+static void set_state (WockyJingleSession *sess,
+ WockyJingleState state, WockyJingleReason termination_reason, const gchar *text);
+static WockyJingleContent *_get_any_content (WockyJingleSession *session);
+
+gboolean
+wocky_jingle_session_peer_has_cap (
+ WockyJingleSession *self,
+ const gchar *cap_or_quirk)
+{
+ WockyJingleSessionPrivate *priv = self->priv;
+ gboolean ret;
+
+ g_signal_emit (self, signals[QUERY_CAP], 0,
+ priv->peer_contact, cap_or_quirk,
+ &ret);
+ return ret;
+}
+
+static gboolean
+lookup_content (WockyJingleSession *sess,
+ const gchar *name,
+ const gchar *creator,
+ gboolean fail_if_missing,
+ WockyJingleContent **c,
+ GError **error)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+
+ if (name == NULL)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "'name' attribute unset");
+ return FALSE;
+ }
+
+ if (WOCKY_JINGLE_DIALECT_IS_GOOGLE (priv->dialect))
+ {
+ /* Only the initiator can create contents on GTalk. */
+ *c = g_hash_table_lookup (priv->initiator_contents, name);
+ }
+ else
+ {
+ /* Versions of Gabble between 0.7.16 and 0.7.28 (inclusive) omitted the
+ * 'creator' attribute from transport-info (and possibly other) stanzas.
+ * We try to detect contacts using such a version of Gabble from their
+ * caps; if 'creator' is missing and the peer has that caps flag, we look
+ * up the content in both hashes.
+ *
+ * While this doesn't deal with the case where the content is found in
+ * both hashes, this isn't a problem in practice: the versions of Gabble
+ * we're working around didn't allow this to happen (they'd either reject
+ * the second stream, or let it replace the first, depending on the phase
+ * of the moon, and get kind of confused in the process), and we try to
+ * pick globally-unique content names.
+ */
+ if (creator == NULL &&
+ wocky_jingle_session_peer_has_cap (sess,
+ WOCKY_QUIRK_OMITS_CONTENT_CREATORS))
+ {
+ DEBUG ("working around missing 'creator' attribute");
+
+ *c = g_hash_table_lookup (priv->initiator_contents, name);
+
+ if (*c == NULL)
+ *c = g_hash_table_lookup (priv->responder_contents, name);
+ }
+ else if (!wocky_strdiff (creator, "initiator"))
+ {
+ *c = g_hash_table_lookup (priv->initiator_contents, name);
+ }
+ else if (!wocky_strdiff (creator, "responder"))
+ {
+ *c = g_hash_table_lookup (priv->responder_contents, name);
+ }
+ else
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "'creator' attribute %s",
+ (creator == NULL ? "missing" : "invalid"));
+ return FALSE;
+ }
+ }
+
+ if (fail_if_missing && *c == NULL)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "Content '%s' (created by %s) does not exist", name, creator);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+_foreach_content (WockyJingleSession *sess,
+ WockyNode *node,
+ gboolean fail_if_missing,
+ ContentHandlerFunc func,
+ gpointer user_data,
+ GError **error)
+{
+ WockyJingleContent *c;
+ WockyNode *content_node;
+ WockyNodeIter iter;
+
+ wocky_node_iter_init (&iter, node, "content", NULL);
+ while (wocky_node_iter_next (&iter, &content_node))
+ {
+ if (!lookup_content (sess,
+ wocky_node_get_attribute (content_node, "name"),
+ wocky_node_get_attribute (content_node, "creator"),
+ fail_if_missing, &c, error))
+ return;
+
+ func (sess, c, content_node, user_data, error);
+ if (*error != NULL)
+ return;
+ }
+}
+
+struct idle_content_reject_ctx {
+ WockyJingleSession *session;
+ WockyStanza *msg;
+};
+
+static gboolean
+idle_content_reject (gpointer data)
+{
+ struct idle_content_reject_ctx *ctx = data;
+
+ wocky_jingle_session_send (ctx->session, ctx->msg);
+
+ g_object_unref (ctx->session);
+ g_free (ctx);
+
+ return FALSE;
+}
+
+static void
+fire_idle_content_reject (WockyJingleSession *sess, const gchar *name,
+ const gchar *creator)
+{
+ struct idle_content_reject_ctx *ctx = g_new0 (struct idle_content_reject_ctx, 1);
+ WockyNode *sess_node, *node;
+
+ if (creator == NULL)
+ creator = "";
+
+ ctx->session = g_object_ref (sess);
+ ctx->msg = wocky_jingle_session_new_message (ctx->session,
+ WOCKY_JINGLE_ACTION_CONTENT_REJECT, &sess_node);
+
+ g_debug ("name = %s, initiator = %s", name, creator);
+
+ node = wocky_node_add_child (sess_node, "content");
+ wocky_node_set_attributes (node,
+ "name", name, "creator", creator, NULL);
+
+ /* FIXME: add API for ordering IQs rather than using g_idle_add. */
+ g_idle_add (idle_content_reject, ctx);
+}
+
+static WockyJingleContent *
+create_content (WockyJingleSession *sess, GType content_type,
+ WockyJingleMediaType type, WockyJingleContentSenders senders,
+ const gchar *content_ns, const gchar *transport_ns,
+ const gchar *name, WockyNode *content_node, GError **error)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+ WockyJingleContent *c;
+ GHashTable *contents;
+
+ DEBUG ("session creating new content name %s, type %d", name, type);
+
+ /* FIXME: media-type is introduced by WockyJingleMediaRTP, not by the
+ * superclass, so this call is unsafe in the general case */
+ c = g_object_new (content_type,
+ "session", sess,
+ "content-ns", content_ns,
+ "transport-ns", transport_ns,
+ "media-type", type,
+ "name", name,
+ "disposition", "session",
+ "senders", senders,
+ NULL);
+
+ g_signal_connect (c, "ready",
+ (GCallback) content_ready_cb, sess);
+ g_signal_connect (c, "removed",
+ (GCallback) content_removed_cb, sess);
+
+ /* if we are called by parser, parse content add */
+ if (content_node != NULL)
+ {
+ wocky_jingle_content_parse_add (c, content_node,
+ WOCKY_JINGLE_DIALECT_IS_GOOGLE (priv->dialect), error);
+
+ if (*error != NULL)
+ {
+ g_object_unref (c);
+ return NULL;
+ }
+
+ /* gtalk streams don't have name, so use whatever Content came up with */
+ if (name == NULL)
+ name = wocky_jingle_content_get_name (c);
+ }
+
+ if (priv->local_initiator == wocky_jingle_content_is_created_by_us (c))
+ {
+ DEBUG ("inserting content %s into initiator_contents", name);
+ contents = priv->initiator_contents;
+ }
+ else
+ {
+ DEBUG ("inserting content %s into responder_contents", name);
+ contents = priv->responder_contents;
+ }
+
+ /* If the content already existed, either we shouldn't have picked the name
+ * we did (if we're creating it) or _each_content_add should have already
+ * said no.
+ */
+ g_assert (g_hash_table_lookup (contents, name) == NULL);
+ g_hash_table_insert (contents, g_strdup (name), c);
+ g_signal_emit (sess, signals[NEW_CONTENT], 0, c);
+ return c;
+}
+
+
+static void
+_each_content_add (WockyJingleSession *sess, WockyJingleContent *c,
+ WockyNode *content_node, gpointer user_data, GError **error)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+ const gchar *name = wocky_node_get_attribute (content_node, "name");
+ WockyNode *desc_node = wocky_node_get_child (content_node,
+ "description");
+ GType content_type = 0;
+ const gchar *content_ns = NULL;
+
+ if (desc_node != NULL)
+ {
+ content_ns = wocky_node_get_ns (desc_node);
+ DEBUG ("namespace: %s", content_ns);
+ content_type = wocky_jingle_factory_lookup_content_type (
+ wocky_jingle_session_get_factory (sess),
+ content_ns);
+ }
+
+ if (content_type == 0)
+ {
+ /* if this is session-initiate, we should return error, otherwise,
+ * we should respond with content-reject */
+ if (priv->state < WOCKY_JINGLE_STATE_PENDING_INITIATED)
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "unsupported content type with ns %s", content_ns);
+ else
+ fire_idle_content_reject (sess, name,
+ wocky_node_get_attribute (content_node, "creator"));
+
+ return;
+ }
+
+ if (c != NULL)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "content '%s' already exists", name);
+ return;
+ }
+
+ create_content (sess, content_type, WOCKY_JINGLE_MEDIA_TYPE_NONE,
+ WOCKY_JINGLE_CONTENT_SENDERS_BOTH, content_ns, NULL, NULL, content_node,
+ error);
+}
+
+static void
+_each_content_remove (WockyJingleSession *sess, WockyJingleContent *c,
+ WockyNode *content_node, gpointer user_data, GError **error)
+{
+ g_assert (c != NULL);
+
+ wocky_jingle_content_remove (c, FALSE);
+}
+
+static void
+_each_content_rejected (WockyJingleSession *sess, WockyJingleContent *c,
+ WockyNode *content_node, gpointer user_data, GError **error)
+{
+ WockyJingleReason reason = GPOINTER_TO_UINT (user_data);
+ g_assert (c != NULL);
+
+ g_signal_emit (sess, signals[CONTENT_REJECTED], 0, c, reason, "");
+
+ wocky_jingle_content_remove (c, FALSE);
+}
+
+static void
+_each_content_modify (WockyJingleSession *sess, WockyJingleContent *c,
+ WockyNode *content_node, gpointer user_data, GError **error)
+{
+ g_assert (c != NULL);
+
+ wocky_jingle_content_update_senders (c, content_node, error);
+
+ if (*error != NULL)
+ return;
+}
+
+static void
+_each_content_replace (WockyJingleSession *sess, WockyJingleContent *c,
+ WockyNode *content_node, gpointer user_data, GError **error)
+{
+ _each_content_remove (sess, c, content_node, NULL, error);
+
+ if (*error != NULL)
+ return;
+
+ _each_content_add (sess, c, content_node, NULL, error);
+}
+
+static void
+_each_content_accept (WockyJingleSession *sess, WockyJingleContent *c,
+ WockyNode *content_node, gpointer user_data, GError **error)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+ WockyJingleContentState state;
+
+ g_assert (c != NULL);
+
+ g_object_get (c, "state", &state, NULL);
+ if (state != WOCKY_JINGLE_CONTENT_STATE_SENT)
+ {
+#ifdef ENABLE_DEBUG
+ const gchar *name = wocky_node_get_attribute (content_node, "name");
+ DEBUG ("ignoring content \"%s\"s acceptance for content not in SENT state", name);
+#endif
+ return;
+ }
+
+ wocky_jingle_content_parse_accept (c, content_node,
+ WOCKY_JINGLE_DIALECT_IS_GOOGLE (priv->dialect), error);
+}
+
+static void
+_each_description_info (WockyJingleSession *sess, WockyJingleContent *c,
+ WockyNode *content_node, gpointer user_data, GError **error)
+{
+ wocky_jingle_content_parse_description_info (c, content_node, error);
+}
+
+static void
+on_session_initiate (WockyJingleSession *sess, WockyNode *node,
+ GError **error)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+
+ /* we can't call ourselves at the moment */
+ if (priv->local_initiator)
+ {
+ /* We ignore initiate from us, and terminate the session immediately
+ * afterwards */
+ wocky_jingle_session_terminate (sess, WOCKY_JINGLE_REASON_BUSY, NULL, NULL);
+ return;
+ }
+
+ if ((priv->dialect == WOCKY_JINGLE_DIALECT_GTALK3))
+ {
+ const gchar *content_ns = NULL;
+ WockyNode *desc_node =
+ wocky_node_get_child (node, "description");
+ content_ns = wocky_node_get_ns (desc_node);
+
+ if (!wocky_strdiff (content_ns, WOCKY_XMPP_NS_GOOGLE_SESSION_VIDEO))
+ {
+ WockyJingleFactory *factory =
+ wocky_jingle_session_get_factory (sess);
+ GType content_type = 0;
+
+ DEBUG ("GTalk v3 session with audio and video");
+
+ /* audio and video content */
+ content_type = wocky_jingle_factory_lookup_content_type (
+ factory, content_ns);
+ create_content (sess, content_type, WOCKY_JINGLE_MEDIA_TYPE_VIDEO,
+ WOCKY_JINGLE_CONTENT_SENDERS_BOTH, WOCKY_XMPP_NS_GOOGLE_SESSION_VIDEO, NULL,
+ "video", node, error);
+
+ content_type = wocky_jingle_factory_lookup_content_type (
+ factory, WOCKY_XMPP_NS_GOOGLE_SESSION_PHONE);
+ create_content (sess, content_type, WOCKY_JINGLE_MEDIA_TYPE_AUDIO,
+ WOCKY_JINGLE_CONTENT_SENDERS_BOTH, WOCKY_XMPP_NS_GOOGLE_SESSION_PHONE, NULL,
+ "audio", node, error);
+ }
+ else
+ {
+ _each_content_add (sess, NULL, node, NULL, error);
+ }
+ }
+ else if (priv->dialect == WOCKY_JINGLE_DIALECT_GTALK4)
+ {
+ /* in this case we implicitly have just one content */
+ _each_content_add (sess, NULL, node, NULL, error);
+ }
+ else
+ {
+ _foreach_content (sess, node, FALSE, _each_content_add, NULL, error);
+ }
+
+ if (*error == NULL)
+ {
+ /* FIXME: contents defined here should always have "session" content
+ * disposition; resolve this as soon as the proper procedure is defined
+ * in XEP-0166. */
+
+ set_state (sess, WOCKY_JINGLE_STATE_PENDING_INITIATED, WOCKY_JINGLE_REASON_UNKNOWN,
+ NULL);
+
+ wocky_jingle_session_send_rtp_info (sess, "ringing");
+ }
+}
+
+static void
+on_content_add (WockyJingleSession *sess, WockyNode *node,
+ GError **error)
+{
+ _foreach_content (sess, node, FALSE, _each_content_add, NULL, error);
+}
+
+static void
+on_content_modify (WockyJingleSession *sess, WockyNode *node,
+ GError **error)
+{
+ _foreach_content (sess, node, TRUE, _each_content_modify, NULL, error);
+}
+
+static void
+on_content_remove (WockyJingleSession *sess, WockyNode *node,
+ GError **error)
+{
+ _foreach_content (sess, node, TRUE, _each_content_remove, NULL, error);
+}
+
+static void
+on_content_replace (WockyJingleSession *sess, WockyNode *node,
+ GError **error)
+{
+ _foreach_content (sess, node, TRUE, _each_content_replace, NULL, error);
+}
+
+static void
+on_content_reject (WockyJingleSession *sess, WockyNode *node,
+ GError **error)
+{
+ WockyNode *n = wocky_node_get_child (node, "reason");
+ WockyJingleReason reason = WOCKY_JINGLE_REASON_UNKNOWN;
+
+ DEBUG (" ");
+
+ if (n != NULL)
+ extract_reason (n, &reason, NULL);
+
+ if (reason == WOCKY_JINGLE_REASON_UNKNOWN)
+ reason = WOCKY_JINGLE_REASON_GENERAL_ERROR;
+
+ _foreach_content (sess, node, TRUE, _each_content_rejected,
+ GUINT_TO_POINTER (reason), error);
+}
+
+static void
+on_content_accept (WockyJingleSession *sess, WockyNode *node,
+ GError **error)
+{
+ _foreach_content (sess, node, TRUE, _each_content_accept, NULL, error);
+}
+
+static void
+on_session_accept (WockyJingleSession *sess, WockyNode *node,
+ GError **error)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+
+ DEBUG ("called");
+
+ if ((priv->dialect == WOCKY_JINGLE_DIALECT_GTALK3) ||
+ (priv->dialect == WOCKY_JINGLE_DIALECT_GTALK4))
+ {
+ /* Google Talk calls don't have contents per se; they just have
+ * <payload-type>s in different namespaces for audio and video, in the
+ * same <description> stanza. So we need to feed the whole stanza to each
+ * content in turn.
+ */
+ GList *cs = wocky_jingle_session_get_contents (sess);
+ GList *l;
+
+ for (l = cs; l != NULL; l = l->next)
+ _each_content_accept (sess, l->data, node, NULL, error);
+
+ g_list_free (cs);
+ }
+ else
+ {
+ _foreach_content (sess, node, TRUE, _each_content_accept, NULL, error);
+ }
+
+ if (*error != NULL)
+ return;
+
+ set_state (sess, WOCKY_JINGLE_STATE_ACTIVE, WOCKY_JINGLE_REASON_UNKNOWN, NULL);
+
+ /* Make sure each content knows the session is active */
+ g_list_foreach (wocky_jingle_session_get_contents (sess),
+ (GFunc) g_object_notify, "state");
+
+
+ if (priv->dialect != WOCKY_JINGLE_DIALECT_V032)
+ {
+ /* If this is a dialect that doesn't support <active/>, we treat
+ * session-accept as the cue to remove the ringing flag.
+ */
+ priv->remote_ringing = FALSE;
+ g_signal_emit (sess, signals[REMOTE_STATE_CHANGED], 0);
+ }
+}
+
+static void
+mute_all_foreach (gpointer key,
+ gpointer value,
+ gpointer mute)
+{
+ if (G_OBJECT_TYPE (value) == WOCKY_TYPE_JINGLE_MEDIA_RTP)
+ g_object_set (value, "remote-mute", GPOINTER_TO_INT (mute), NULL);
+}
+
+static void
+mute_all (WockyJingleSession *sess,
+ gboolean mute)
+{
+ g_hash_table_foreach (sess->priv->initiator_contents, mute_all_foreach,
+ GINT_TO_POINTER (mute));
+ g_hash_table_foreach (sess->priv->responder_contents, mute_all_foreach,
+ GINT_TO_POINTER (mute));
+}
+
+static gboolean
+set_mute (WockyJingleSession *sess,
+ const gchar *name,
+ const gchar *creator,
+ gboolean mute,
+ GError **error)
+{
+ WockyJingleContent *c;
+
+ if (name == NULL)
+ {
+ mute_all (sess, mute);
+ return TRUE;
+ }
+
+ if (!lookup_content (sess, name, creator, TRUE /* fail if missing */, &c,
+ error))
+ return FALSE;
+
+ if (G_OBJECT_TYPE (c) != WOCKY_TYPE_JINGLE_MEDIA_RTP)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "content '%s' isn't an RTP session", name);
+ return FALSE;
+ }
+
+ g_object_set (c, "remote-mute", mute, NULL);
+ return TRUE;
+}
+
+static void
+set_hold (WockyJingleSession *sess,
+ gboolean hold)
+{
+ sess->priv->remote_hold = hold;
+}
+
+static void
+set_ringing (WockyJingleSession *sess,
+ gboolean ringing)
+{
+ sess->priv->remote_ringing = ringing;
+}
+
+static gboolean
+handle_payload (WockyJingleSession *sess,
+ WockyNode *payload,
+ gboolean *handled,
+ GError **error)
+{
+ const gchar *ns = wocky_node_get_ns (payload);
+ const gchar *elt = payload->name;
+ const gchar *name = wocky_node_get_attribute (payload, "name");
+ const gchar *creator = wocky_node_get_attribute (payload, "creator");
+
+ if (wocky_strdiff (ns, WOCKY_XMPP_NS_JINGLE_RTP_INFO))
+ {
+ *handled = FALSE;
+ return TRUE;
+ }
+
+ *handled = TRUE;
+
+ if (!wocky_strdiff (elt, "active"))
+ {
+ /* Clear all states, we're active */
+ mute_all (sess, FALSE);
+ set_ringing (sess, FALSE);
+ set_hold (sess, FALSE);
+ }
+ else if (!wocky_strdiff (elt, "ringing"))
+ {
+ set_ringing (sess, TRUE);
+ }
+ else if (!wocky_strdiff (elt, "hold"))
+ {
+ set_hold (sess, TRUE);
+ }
+ else if (!wocky_strdiff (elt, "unhold"))
+ {
+ set_hold (sess, FALSE);
+ }
+ /* XEP-0178 says that only <mute/> and <unmute/> can have a name=''
+ * attribute.
+ */
+ else if (!wocky_strdiff (elt, "mute"))
+ {
+ return set_mute (sess, name, creator, TRUE, error);
+ }
+ else if (!wocky_strdiff (elt, "unmute"))
+ {
+ return set_mute (sess, name, creator, FALSE, error);
+ }
+ else
+ {
+ g_set_error (error, WOCKY_JINGLE_ERROR,
+ WOCKY_JINGLE_ERROR_UNSUPPORTED_INFO,
+ "<%s> is not known in namespace %s", elt, ns);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+on_session_info (WockyJingleSession *sess,
+ WockyNode *node,
+ GError **error)
+{
+ gboolean understood_a_payload = FALSE;
+ gboolean hit_an_error = FALSE;
+ WockyNodeIter i;
+ WockyNode *n;
+
+ /* if this is a ping, just ack it. */
+ if (wocky_node_get_first_child (node) == NULL)
+ return;
+
+ wocky_node_iter_init (&i, node, NULL, NULL);
+ while (wocky_node_iter_next (&i, &n))
+ {
+ gboolean handled;
+ GError *e = NULL;
+
+ if (handle_payload (sess, n, &handled, &e))
+ {
+ understood_a_payload = understood_a_payload || handled;
+ }
+ else if (hit_an_error)
+ {
+ DEBUG ("already got another error; ignoring %s", e->message);
+ g_error_free (e);
+ }
+ else
+ {
+ DEBUG ("hit an error: %s", e->message);
+ hit_an_error = TRUE;
+ g_propagate_error (error, e);
+ }
+ }
+
+ /* If we understood something, the remote state (may have) changed. Else,
+ * return an error to the peer.
+ */
+ if (understood_a_payload)
+ g_signal_emit (sess, signals[REMOTE_STATE_CHANGED], 0);
+ else if (!hit_an_error)
+ g_set_error (error, WOCKY_JINGLE_ERROR, WOCKY_JINGLE_ERROR_UNSUPPORTED_INFO,
+ "no recognized session-info payloads");
+}
+
+static void
+on_session_terminate (WockyJingleSession *sess, WockyNode *node,
+ GError **error)
+{
+ gchar *text = NULL;
+ WockyNode *n = wocky_node_get_child (node, "reason");
+ WockyJingleReason wocky_jingle_reason = WOCKY_JINGLE_REASON_UNKNOWN;
+
+ if (n != NULL)
+ extract_reason (n, &wocky_jingle_reason, &text);
+
+ DEBUG ("remote end terminated the session with reason %s and text '%s'",
+ wocky_jingle_session_get_reason_name (wocky_jingle_reason),
+ (text != NULL ? text : "(none)"));
+
+ set_state (sess, WOCKY_JINGLE_STATE_ENDED, wocky_jingle_reason, text);
+
+ g_free (text);
+}
+
+static void
+on_transport_info (WockyJingleSession *sess, WockyNode *node,
+ GError **error)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+ WockyJingleContent *c = NULL;
+
+ if (WOCKY_JINGLE_DIALECT_IS_GOOGLE (priv->dialect))
+ {
+ GHashTableIter iter;
+ gpointer value;
+
+ if (priv->dialect == WOCKY_JINGLE_DIALECT_GTALK4)
+ {
+ if (!wocky_strdiff (wocky_node_get_attribute (node, "type"),
+ "candidates"))
+ {
+ GList *contents = wocky_jingle_session_get_contents (sess);
+ GList *l;
+
+ DEBUG ("switching to gtalk3 dialect and retransmiting our candidates");
+ priv->dialect = WOCKY_JINGLE_DIALECT_GTALK3;
+
+ for (l = contents; l != NULL; l = l->next)
+ wocky_jingle_content_retransmit_candidates (l->data, TRUE);
+
+ g_list_free (contents);
+ }
+ else
+ {
+ node = wocky_node_get_child (node, "transport");
+
+ if (node == NULL)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "transport-info stanza without a <transport/>");
+ return;
+ }
+ }
+ }
+
+ g_hash_table_iter_init (&iter, priv->initiator_contents);
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ c = value;
+ wocky_jingle_content_parse_transport_info (c, node, error);
+ if (error != NULL && *error != NULL)
+ break;
+ }
+ }
+ else
+ {
+ WockyNodeIter i;
+ WockyNode *content_node;
+ GError *e = NULL;
+
+ wocky_node_iter_init (&i, node, "content", NULL);
+
+ while (wocky_node_iter_next (&i, &content_node))
+ {
+ WockyNode *transport_node;
+
+ if (lookup_content (sess,
+ wocky_node_get_attribute (content_node, "name"),
+ wocky_node_get_attribute (content_node, "creator"),
+ TRUE /* fail_if_missing */, &c, &e))
+ {
+ /* we need transport child of content node */
+ transport_node = wocky_node_get_child (
+ content_node, "transport");
+ wocky_jingle_content_parse_transport_info (c,
+ transport_node, &e);
+ }
+
+ /* Save the first error we encounter, but go through all remaining
+ * contents anyway to try and recover as much info as we can */
+ if (e != NULL && error != NULL && *error == NULL)
+ {
+ *error = e;
+ e = NULL;
+ }
+ g_clear_error (&e);
+ }
+ }
+
+}
+
+static void
+on_transport_accept (WockyJingleSession *sess, WockyNode *node,
+ GError **error)
+{
+ DEBUG ("Ignoring 'transport-accept' action from peer");
+}
+
+static void
+on_description_info (WockyJingleSession *sess, WockyNode *node,
+ GError **error)
+{
+ _foreach_content (sess, node, TRUE, _each_description_info, NULL, error);
+}
+
+static void
+on_info (WockyJingleSession *sess, WockyNode *node,
+ GError **error)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+ WockyJingleContent *c = NULL;
+
+ DEBUG ("received info ");
+ if (WOCKY_JINGLE_DIALECT_IS_GOOGLE (priv->dialect))
+ {
+ GHashTableIter iter;
+ g_hash_table_iter_init (&iter, priv->initiator_contents);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer) &c))
+ {
+ wocky_jingle_content_parse_info (c, node, error);
+ if (error != NULL && *error != NULL)
+ break;
+ }
+ }
+}
+
+static HandlerFunc handlers[] = {
+ NULL, /* for unknown action */
+ on_content_accept,
+ on_content_add,
+ on_content_modify,
+ on_content_remove,
+ on_content_replace,
+ on_content_reject,
+ on_session_accept, /* jingle_on_session_accept */
+ on_session_info,
+ on_session_initiate,
+ on_session_terminate, /* jingle_on_session_terminate */
+ on_transport_info, /* jingle_on_transport_info */
+ on_transport_accept,
+ on_description_info,
+ on_info
+};
+
+static void
+wocky_jingle_state_machine_dance (WockyJingleSession *sess,
+ WockyJingleAction action,
+ WockyNode *node,
+ GError **error)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+
+ /* parser should've checked this already */
+ g_assert (action_is_allowed (action, priv->state));
+ g_assert (handlers[action] != NULL);
+
+ handlers[action] (sess, node, error);
+}
+
+static WockyJingleDialect
+detect_google_dialect (WockyNode *session_node)
+{
+ /* The GTALK3 dialect is the only one that supports video at this time */
+ if (wocky_node_get_child_ns (session_node,
+ "description", WOCKY_XMPP_NS_GOOGLE_SESSION_VIDEO) != NULL)
+ return WOCKY_JINGLE_DIALECT_GTALK3;
+
+ /* GTalk4 has a transport item, GTalk3 doesn't */
+ if (wocky_node_get_child_ns (session_node,
+ "transport", WOCKY_XMPP_NS_GOOGLE_TRANSPORT_P2P) == NULL)
+ return WOCKY_JINGLE_DIALECT_GTALK3;
+
+ return WOCKY_JINGLE_DIALECT_GTALK4;
+}
+
+const gchar *
+wocky_jingle_session_detect (
+ WockyStanza *stanza,
+ WockyJingleAction *action,
+ WockyJingleDialect *dialect)
+{
+ const gchar *actxt, *sid;
+ WockyNode *iq_node, *session_node;
+ WockyStanzaSubType sub_type;
+ gboolean google_mode = FALSE;
+
+ /* all jingle actions are sets */
+ wocky_stanza_get_type_info (stanza, NULL, &sub_type);
+ if (sub_type != WOCKY_STANZA_SUB_TYPE_SET)
+ return NULL;
+
+ iq_node = wocky_stanza_get_top_node (stanza);
+
+ if ((NULL == wocky_stanza_get_from (stanza)) ||
+ (NULL == wocky_stanza_get_to (stanza)))
+ return NULL;
+
+ /* first, we try standard jingle */
+ session_node = wocky_node_get_child_ns (iq_node, "jingle", WOCKY_XMPP_NS_JINGLE);
+
+ if (session_node != NULL)
+ {
+ *dialect = WOCKY_JINGLE_DIALECT_V032;
+ }
+ else
+ {
+ /* then, we try a bit older jingle version */
+ session_node = wocky_node_get_child_ns (iq_node, "jingle", WOCKY_XMPP_NS_JINGLE015);
+
+ if (session_node != NULL)
+ {
+ *dialect = WOCKY_JINGLE_DIALECT_V015;
+ }
+ else
+ {
+ /* next, we try googletalk */
+ session_node = wocky_node_get_child_ns (iq_node,
+ "session", WOCKY_XMPP_NS_GOOGLE_SESSION);
+
+ if (session_node != NULL)
+ {
+ *dialect = detect_google_dialect (session_node);
+ google_mode = TRUE;
+ }
+ else
+ {
+ return NULL;
+ }
+ }
+ }
+
+ if (google_mode)
+ {
+ actxt = wocky_node_get_attribute (session_node, "type");
+ sid = wocky_node_get_attribute (session_node, "id");
+ }
+ else
+ {
+ actxt = wocky_node_get_attribute (session_node, "action");
+ sid = wocky_node_get_attribute (session_node, "sid");
+ }
+
+ *action = parse_action (actxt);
+
+ return sid;
+}
+
+gboolean
+wocky_jingle_session_parse (
+ WockyJingleSession *sess,
+ WockyJingleAction action,
+ WockyStanza *stanza,
+ GError **error)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+ WockyNode *iq_node, *session_node;
+ const gchar *from, *action_name;
+
+ /* IQ from/to can come in handy */
+ from = wocky_stanza_get_from (stanza);
+ iq_node = wocky_stanza_get_top_node (stanza);
+
+ if (action == WOCKY_JINGLE_ACTION_UNKNOWN)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "unknown session action");
+ return FALSE;
+ }
+
+ action_name = produce_action (action, priv->dialect);
+
+ DEBUG ("jingle action '%s' from '%s' in session '%s' dialect %u state %u",
+ action_name, from, priv->sid, priv->dialect, priv->state);
+
+ switch (priv->dialect) {
+ case WOCKY_JINGLE_DIALECT_V032:
+ session_node = wocky_node_get_child_ns (iq_node,
+ "jingle", WOCKY_XMPP_NS_JINGLE);
+ break;
+ case WOCKY_JINGLE_DIALECT_V015:
+ session_node = wocky_node_get_child_ns (iq_node,
+ "jingle", WOCKY_XMPP_NS_JINGLE015);
+ break;
+ case WOCKY_JINGLE_DIALECT_GTALK3:
+ case WOCKY_JINGLE_DIALECT_GTALK4:
+ session_node = wocky_node_get_child_ns (iq_node,
+ "session", WOCKY_XMPP_NS_GOOGLE_SESSION);
+ break;
+ default:
+ /* just to make gcc happy about dealing with default case */
+ session_node = NULL;
+ }
+
+ if (session_node == NULL)
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "malformed jingle stanza");
+ return FALSE;
+ }
+
+ if (!wocky_jingle_session_defines_action (sess, action))
+ {
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "action '%s' unknown (using dialect %u)", action_name, priv->dialect);
+ return FALSE;
+ }
+
+ if (!action_is_allowed (action, priv->state))
+ {
+ g_set_error (error, WOCKY_JINGLE_ERROR, WOCKY_JINGLE_ERROR_OUT_OF_ORDER,
+ "action '%s' not allowed in current state", action_name);
+ return FALSE;
+ }
+
+ wocky_jingle_state_machine_dance (sess, action, session_node, error);
+
+ if (*error != NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+WockyStanza *
+wocky_jingle_session_new_message (WockyJingleSession *sess,
+ WockyJingleAction action, WockyNode **sess_node)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+ WockyStanza *stanza;
+ WockyNode *session_node;
+ gchar *el = NULL, *ns = NULL;
+ gboolean gtalk_mode = FALSE;
+
+ g_return_val_if_fail (action != WOCKY_JINGLE_ACTION_UNKNOWN, NULL);
+
+ g_assert ((action == WOCKY_JINGLE_ACTION_SESSION_INITIATE) ||
+ (priv->state > WOCKY_JINGLE_STATE_PENDING_CREATED));
+ g_assert (WOCKY_IS_JINGLE_SESSION (sess));
+
+ switch (priv->dialect)
+ {
+ case WOCKY_JINGLE_DIALECT_V032:
+ el = "jingle";
+ ns = WOCKY_XMPP_NS_JINGLE;
+ break;
+ case WOCKY_JINGLE_DIALECT_V015:
+ el = "jingle";
+ ns = WOCKY_XMPP_NS_JINGLE015;
+ break;
+ case WOCKY_JINGLE_DIALECT_GTALK3:
+ case WOCKY_JINGLE_DIALECT_GTALK4:
+ el = "session";
+ ns = WOCKY_XMPP_NS_GOOGLE_SESSION;
+ gtalk_mode = TRUE;
+ break;
+ case WOCKY_JINGLE_DIALECT_ERROR:
+ g_assert_not_reached ();
+ }
+
+ stanza = wocky_stanza_build (
+ WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET,
+ NULL, priv->peer_jid,
+ '(', el, ':', ns,
+ '*', &session_node,
+ ')', NULL);
+
+ wocky_node_set_attributes (session_node,
+ "initiator", priv->initiator,
+ (gtalk_mode) ? "id" : "sid", priv->sid,
+ (gtalk_mode) ? "type" : "action",
+ produce_action (action, priv->dialect),
+ NULL);
+
+ if (sess_node != NULL)
+ *sess_node = session_node;
+
+ return stanza;
+}
+
+typedef void (*ContentMapperFunc) (WockyJingleSession *sess,
+ WockyJingleContent *c, gpointer user_data);
+
+static void
+_map_initial_contents (WockyJingleSession *sess, ContentMapperFunc mapper,
+ gpointer user_data)
+{
+ GList *li;
+ GList *contents = wocky_jingle_session_get_contents (sess);
+
+ for (li = contents; li; li = li->next)
+ {
+ WockyJingleContent *c = WOCKY_JINGLE_CONTENT (li->data);
+ const gchar *disposition = wocky_jingle_content_get_disposition (c);
+
+ if (!wocky_strdiff (disposition, "session"))
+ mapper (sess, c, user_data);
+ }
+
+ g_list_free (contents);
+}
+
+static void
+_check_content_ready (WockyJingleSession *sess,
+ WockyJingleContent *c, gpointer user_data)
+{
+ gboolean *ready = (gboolean *) user_data;
+
+ if (!wocky_jingle_content_is_ready (c))
+ {
+ *ready = FALSE;
+ }
+}
+
+static void
+_transmit_candidates (WockyJingleSession *sess,
+ WockyJingleContent *c,
+ gpointer user_data)
+{
+ wocky_jingle_content_retransmit_candidates (c, FALSE);
+}
+
+static void
+_fill_content (WockyJingleSession *sess,
+ WockyJingleContent *c, gpointer user_data)
+{
+ WockyNode *sess_node = user_data;
+ WockyNode *transport_node;
+ WockyJingleContentState state;
+
+ wocky_jingle_content_produce_node (c, sess_node, TRUE, TRUE,
+ &transport_node);
+ wocky_jingle_content_inject_candidates (c, transport_node);
+
+ g_object_get (c, "state", &state, NULL);
+
+ if (state == WOCKY_JINGLE_CONTENT_STATE_EMPTY)
+ {
+ g_object_set (c, "state", WOCKY_JINGLE_CONTENT_STATE_SENT, NULL);
+ }
+ else if (state == WOCKY_JINGLE_CONTENT_STATE_NEW)
+ {
+ g_object_set (c, "state", WOCKY_JINGLE_CONTENT_STATE_ACKNOWLEDGED, NULL);
+ }
+ else
+ {
+ DEBUG ("content %p is in state %u", c, state);
+ g_assert_not_reached ();
+ }
+}
+
+/**
+ * wocky_jingle_session_send:
+ * @sess: a session
+ * @stanza: (transfer full): a stanza, of which this function will take ownership
+ *
+ * A shorthand for sending a Jingle IQ without waiting for the reply.
+ */
+void
+wocky_jingle_session_send (WockyJingleSession *sess,
+ WockyStanza *stanza)
+{
+ wocky_porter_send_iq_async (sess->priv->porter,
+ stanza, NULL, NULL, NULL);
+ g_object_unref (stanza);
+}
+
+static void
+_on_initiate_reply (
+ GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source);
+ WockyJingleSession *sess = WOCKY_JINGLE_SESSION (user_data);
+ WockyJingleSessionPrivate *priv = sess->priv;
+ WockyStanza *reply;
+
+ if (priv->state != WOCKY_JINGLE_STATE_PENDING_INITIATE_SENT)
+ {
+ DEBUG ("Ignoring session-initiate reply; session %p is in state %u.",
+ sess, priv->state);
+ g_object_unref (sess);
+ return;
+ }
+
+ reply = wocky_porter_send_iq_finish (porter, result, NULL);
+ if (reply != NULL &&
+ !wocky_stanza_extract_errors (reply, NULL, NULL, NULL, NULL))
+ {
+ set_state (sess, WOCKY_JINGLE_STATE_PENDING_INITIATED, 0, NULL);
+
+ if (priv->dialect != WOCKY_JINGLE_DIALECT_V032)
+ {
+ /* If this is a dialect that doesn't support <ringing/>, we treat the
+ * session-initiate being acked as the cue to say we're ringing.
+ */
+ priv->remote_ringing = TRUE;
+ g_signal_emit (sess, signals[REMOTE_STATE_CHANGED], 0);
+ }
+ }
+ else
+ {
+ set_state (sess, WOCKY_JINGLE_STATE_ENDED, WOCKY_JINGLE_REASON_UNKNOWN,
+ NULL);
+ }
+
+ g_clear_object (&reply);
+ g_object_unref (sess);
+}
+
+static void
+_on_accept_reply (
+ GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source);
+ WockyJingleSession *sess = WOCKY_JINGLE_SESSION (user_data);
+ WockyJingleSessionPrivate *priv = sess->priv;
+ WockyStanza *reply;
+
+ if (priv->state != WOCKY_JINGLE_STATE_PENDING_ACCEPT_SENT)
+ {
+ DEBUG ("Ignoring session-accept reply; session %p is in state %u.",
+ sess, priv->state);
+ g_object_unref (sess);
+ return;
+ }
+
+ reply = wocky_porter_send_iq_finish (porter, result, NULL);
+ if (reply != NULL &&
+ !wocky_stanza_extract_errors (reply, NULL, NULL, NULL, NULL))
+ {
+ set_state (sess, WOCKY_JINGLE_STATE_ACTIVE, 0, NULL);
+ wocky_jingle_session_send_rtp_info (sess, "active");
+ }
+ else
+ {
+ set_state (sess, WOCKY_JINGLE_STATE_ENDED, WOCKY_JINGLE_REASON_UNKNOWN,
+ NULL);
+ }
+
+ g_clear_object (&reply);
+ g_object_unref (sess);
+}
+
+static void
+try_session_initiate_or_accept (WockyJingleSession *sess)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+ WockyStanza *msg;
+ WockyNode *sess_node;
+ gboolean contents_ready = TRUE;
+ WockyJingleAction action;
+ WockyJingleState new_state;
+ GAsyncReadyCallback handler;
+
+ DEBUG ("Trying initiate or accept");
+
+ /* If there are no contents yet, we shouldn't have been called at all. */
+ g_assert (g_hash_table_size (priv->initiator_contents) +
+ g_hash_table_size (priv->responder_contents) > 0);
+
+ if (priv->local_initiator)
+ {
+ if (priv->state != WOCKY_JINGLE_STATE_PENDING_CREATED)
+ {
+ DEBUG ("session is in state %u, won't try to initiate", priv->state);
+ return;
+ }
+
+ if (!priv->locally_accepted)
+ {
+ DEBUG ("session not locally accepted yet, not initiating");
+ return;
+ }
+
+ action = WOCKY_JINGLE_ACTION_SESSION_INITIATE;
+ new_state = WOCKY_JINGLE_STATE_PENDING_INITIATE_SENT;
+ handler = _on_initiate_reply;
+ }
+ else
+ {
+ if (priv->state != WOCKY_JINGLE_STATE_PENDING_INITIATED)
+ {
+ DEBUG ("session is in state %u, won't try to accept", priv->state);
+ return;
+ }
+
+ if (!priv->locally_accepted)
+ {
+ DEBUG ("session not locally accepted yet, not accepting");
+ return;
+ }
+
+ action = WOCKY_JINGLE_ACTION_SESSION_ACCEPT;
+ new_state = WOCKY_JINGLE_STATE_PENDING_ACCEPT_SENT;
+ handler = _on_accept_reply;
+ }
+
+ _map_initial_contents (sess, _check_content_ready, &contents_ready);
+
+ DEBUG ("Contents are ready: %s", contents_ready ? "yes" : "no");
+
+ if (!contents_ready)
+ {
+ DEBUG ("Contents not yet ready, not initiating/accepting now..");
+ return;
+ }
+
+ if (action == WOCKY_JINGLE_ACTION_SESSION_INITIATE)
+ g_signal_emit (sess, signals[ABOUT_TO_INITIATE], 0);
+
+ msg = wocky_jingle_session_new_message (sess, action, &sess_node);
+
+ if (priv->dialect == WOCKY_JINGLE_DIALECT_GTALK3)
+ {
+ gboolean has_video = FALSE;
+ gboolean has_audio = FALSE;
+ GHashTableIter iter;
+ gpointer value;
+
+ g_hash_table_iter_init (&iter, priv->initiator_contents);
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ WockyJingleMediaType type;
+
+ g_object_get (value, "media-type", &type, NULL);
+
+ if (type == WOCKY_JINGLE_MEDIA_TYPE_VIDEO)
+ {
+ has_video = TRUE;
+ }
+ else if (type == WOCKY_JINGLE_MEDIA_TYPE_AUDIO)
+ {
+ has_audio = TRUE;
+ }
+ }
+
+ if (has_video || has_audio)
+ {
+ sess_node = wocky_node_add_child_ns_q (sess_node, "description",
+ g_quark_from_static_string (has_video
+ ? WOCKY_XMPP_NS_GOOGLE_SESSION_VIDEO : WOCKY_XMPP_NS_GOOGLE_SESSION_PHONE));
+ }
+ }
+
+
+ _map_initial_contents (sess, _fill_content, sess_node);
+ wocky_porter_send_iq_async (priv->porter,
+ msg, NULL, handler, g_object_ref (sess));
+ g_object_unref (msg);
+ set_state (sess, new_state, 0, NULL);
+
+ /* now all initial contents can transmit their candidates */
+ _map_initial_contents (sess, _transmit_candidates, NULL);
+}
+
+/**
+ * set_state:
+ * @sess: a jingle session
+ * @state: the new state for the session
+ * @termination_reason: if @state is WOCKY_JINGLE_STATE_ENDED, the reason the session
+ * ended. Otherwise, must be WOCKY_JINGLE_REASON_UNKNOWN.
+ * @text: if @state is WOCKY_JINGLE_STATE_ENDED, the human-readable reason the session
+ * ended.
+ */
+static void
+set_state (WockyJingleSession *sess,
+ WockyJingleState state,
+ WockyJingleReason termination_reason,
+ const gchar *text)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+
+ if (state <= priv->state)
+ {
+ DEBUG ("ignoring request to set state from %u back to %u", priv->state, state);
+ return;
+ }
+
+ if (state != WOCKY_JINGLE_STATE_ENDED)
+ g_assert (termination_reason == WOCKY_JINGLE_REASON_UNKNOWN);
+
+ DEBUG ("Setting state of JingleSession: %p (priv = %p) from %u to %u", sess, priv, priv->state, state);
+
+ priv->state = state;
+ g_object_notify (G_OBJECT (sess), "state");
+
+ /* If we have an outstanding "you're on hold notification", send it */
+ if (priv->local_hold &&
+ state >= WOCKY_JINGLE_STATE_PENDING_INITIATED &&
+ state < WOCKY_JINGLE_STATE_ENDED)
+ wocky_jingle_session_send_held (sess);
+
+ if (state == WOCKY_JINGLE_STATE_ENDED)
+ g_signal_emit (sess, signals[TERMINATED], 0, priv->locally_terminated,
+ termination_reason, text);
+}
+
+
+/**
+ * wocky_jingle_session_accept:
+ * @sess: the session.
+ *
+ * For incoming calls, accepts the call. For outgoing calls, indicates that the
+ * initial contents for the call have been created and the offer can be sent to
+ * the peer.
+ *
+ * The acceptance or offer will only be signalled to the peer once all contents
+ * are ready (as returned by wocky_jingle_content_is_ready()). For an RTP
+ * session with #WockyJingleMediaRtp contents, this translates to a media
+ * description and transport candidates having been provided to all contents.
+ */
+void
+wocky_jingle_session_accept (WockyJingleSession *sess)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+
+ priv->locally_accepted = TRUE;
+
+ try_session_initiate_or_accept (sess);
+}
+
+const gchar *
+wocky_jingle_session_get_reason_name (WockyJingleReason reason)
+{
+ GEnumClass *klass = g_type_class_ref (wocky_jingle_reason_get_type ());
+ GEnumValue *enum_value = g_enum_get_value (klass, (gint) reason);
+
+ g_return_val_if_fail (enum_value != NULL, NULL);
+
+ return enum_value->value_nick;
+}
+
+
+/**
+ * wocky_jingle_session_terminate:
+ * @sess: the session
+ * @reason: the reason the session should be terminated
+ * @text: (allow-none): human-readable information about why the session
+ * terminated
+ * @error: Unused, because this function never fails.
+ *
+ * Ends a session.
+ *
+ * If called for an outgoing session which has not yet been signalled to the
+ * peer (perhaps because wocky_jingle_session_accept() has not been called, or
+ * codecs or candidates have not been provided), the session will quietly
+ * terminate without the peer hearing anything about it.
+ *
+ * If called for an already-terminated session, this is a no-op.
+ *
+ * Returns: %TRUE.
+ */
+gboolean
+wocky_jingle_session_terminate (WockyJingleSession *sess,
+ WockyJingleReason reason,
+ const gchar *text,
+ GError **error G_GNUC_UNUSED)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+ const gchar *reason_elt;
+
+ if (priv->state == WOCKY_JINGLE_STATE_ENDED)
+ {
+ DEBUG ("session already terminated, ignoring terminate request");
+ return TRUE;
+ }
+
+ if (reason == WOCKY_JINGLE_REASON_UNKNOWN)
+ reason = (priv->state == WOCKY_JINGLE_STATE_ACTIVE) ?
+ WOCKY_JINGLE_REASON_SUCCESS : WOCKY_JINGLE_REASON_CANCEL;
+
+ reason_elt = wocky_jingle_session_get_reason_name (reason);
+
+ if (priv->state != WOCKY_JINGLE_STATE_PENDING_CREATED)
+ {
+ WockyNode *session_node;
+ WockyStanza *msg = wocky_jingle_session_new_message (sess,
+ WOCKY_JINGLE_ACTION_SESSION_TERMINATE, &session_node);
+
+ if (priv->dialect == WOCKY_JINGLE_DIALECT_V032 && reason_elt != NULL)
+ {
+ WockyNode *r = wocky_node_add_child_with_content (session_node, "reason",
+ NULL);
+
+ wocky_node_add_child (r, reason_elt);
+
+ if (text != NULL && *text != '\0')
+ wocky_node_add_child_with_content (r, "text", text);
+ }
+
+ wocky_jingle_session_send (sess, msg);
+ }
+
+ /* NOTE: on "terminated", jingle factory and media channel will unref
+ * it, bringing refcount to 0, so dispose will be called, and it
+ * takes care of cleanup */
+
+ DEBUG ("we are terminating this session");
+ priv->locally_terminated = TRUE;
+ set_state (sess, WOCKY_JINGLE_STATE_ENDED, reason, text);
+
+ return TRUE;
+}
+
+static void
+_foreach_count_active_contents (gpointer key, gpointer value, gpointer user_data)
+{
+ WockyJingleContent *c = value;
+ guint *n_contents = user_data;
+ WockyJingleContentState state;
+
+ g_object_get (c, "state", &state, NULL);
+ if ((state >= WOCKY_JINGLE_CONTENT_STATE_NEW) &&
+ (state < WOCKY_JINGLE_CONTENT_STATE_REMOVING))
+ {
+ *n_contents = *n_contents + 1;
+ }
+}
+
+static gboolean
+count_active_contents (WockyJingleSession *sess)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+ guint n_contents = 0;
+
+ g_hash_table_foreach (priv->initiator_contents, _foreach_count_active_contents,
+ &n_contents);
+ g_hash_table_foreach (priv->responder_contents, _foreach_count_active_contents,
+ &n_contents);
+
+ return n_contents;
+}
+
+static void
+content_removed_cb (WockyJingleContent *c, gpointer user_data)
+{
+ WockyJingleSession *sess = WOCKY_JINGLE_SESSION (user_data);
+ WockyJingleSessionPrivate *priv = sess->priv;
+ const gchar *name = wocky_jingle_content_get_name (c);
+
+ if (wocky_jingle_content_creator_is_initiator (c))
+ g_hash_table_remove (priv->initiator_contents, name);
+ else
+ g_hash_table_remove (priv->responder_contents, name);
+
+ if (priv->state == WOCKY_JINGLE_STATE_ENDED)
+ return;
+
+ if (count_active_contents (sess) == 0)
+ {
+ wocky_jingle_session_terminate (sess,
+ WOCKY_JINGLE_REASON_UNKNOWN, NULL, NULL);
+ }
+ else
+ {
+ /* It's possible the content now removed was
+ * blocking us from creating or accepting the
+ * session, so we might as well try now. */
+ try_session_initiate_or_accept (sess);
+ }
+}
+
+
+void
+wocky_jingle_session_remove_content (WockyJingleSession *sess,
+ WockyJingleContent *c)
+{
+ if (count_active_contents (sess) > 1)
+ {
+ wocky_jingle_content_remove (c, TRUE);
+ }
+ else
+ {
+ /* session will be terminated when the content gets marked as removed */
+ DEBUG ("called for last active content, doing session-terminate instead");
+ wocky_jingle_content_remove (c, FALSE);
+ }
+}
+
+
+/**
+ * wocky_jingle_session_add_content:
+ * @sess: the session
+ * @mtype: what kind of media will be exchanged on the content
+ * @senders: which directions media should initially flow in.
+ * @name: (allow-none): a descriptive name to use for the content; this is
+ * typically not shown to users
+ * @content_ns: the namespace to use for the content's description
+ * @transport_ns: the namespace of the media transport to use for the call
+ *
+ * Adds a content to the session. Once it has its codecs and transport
+ * candidates filled in, it will be signalled to the peer (either as part of
+ * the session-initiate, if it has not been sent yet, or as a content-add if
+ * @sess has already been initiated).
+ *
+ * Legal values for @content_ns and @transport_ns depend on the Jingle dialect
+ * in use for this session (and in some cases on @mtype); sensible values
+ * depend on the peer's capabilities.
+ *
+ * Returns: (transfer none): the new content, which is guaranteed not to be %NULL.
+ */
+WockyJingleContent *
+wocky_jingle_session_add_content (WockyJingleSession *sess,
+ WockyJingleMediaType mtype,
+ WockyJingleContentSenders senders,
+ const gchar *name,
+ const gchar *content_ns,
+ const gchar *transport_ns)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+ WockyJingleContent *c;
+ GType content_type;
+ GHashTable *contents = priv->local_initiator ? priv->initiator_contents
+ : priv->responder_contents;
+ guint id = g_hash_table_size (contents) + 1;
+ gchar *cname = NULL;
+
+ if (name == NULL || *name == '\0')
+ name = (mtype == WOCKY_JINGLE_MEDIA_TYPE_AUDIO ? "Audio" : "Video");
+
+ cname = g_strdup (name);
+
+ while (g_hash_table_lookup (priv->initiator_contents, cname) != NULL
+ || g_hash_table_lookup (priv->responder_contents, cname) != NULL)
+ {
+ g_free (cname);
+ cname = g_strdup_printf ("%s_%d", name, id++);
+ }
+
+ content_type = wocky_jingle_factory_lookup_content_type (
+ wocky_jingle_session_get_factory (sess),
+ content_ns);
+
+ g_assert (content_type != 0);
+
+ c = create_content (sess, content_type, mtype, senders,
+ content_ns, transport_ns, cname, NULL, NULL);
+
+ /* The new content better have ended up in the set we thought it would... */
+ g_assert (g_hash_table_lookup (contents, cname) != NULL);
+
+ g_free (cname);
+
+ return c;
+}
+
+/* Get any content. Either we're in google mode (so we only have one content
+ * anyways), or we just need any content type to figure out what use case
+ * we're in (media, ft, etc). */
+static WockyJingleContent *
+_get_any_content (WockyJingleSession *session)
+{
+ WockyJingleContent *c;
+
+ GList *li = wocky_jingle_session_get_contents (session);
+
+ if (li == NULL)
+ return NULL;
+
+ c = li->data;
+ g_list_free (li);
+
+ return c;
+}
+
+/* Note: if there are multiple content types, not guaranteed which one will
+ * be returned. Typically, the same GType will know how to handle related
+ * contents found in a session (e.g. media-rtp for audio/video), so that
+ * should not be a problem. Returns 0 if there are no contents yet. */
+GType
+wocky_jingle_session_get_content_type (WockyJingleSession *sess)
+{
+ WockyJingleContent *c = _get_any_content (sess);
+
+ if (c == NULL)
+ return 0;
+
+ return G_OBJECT_TYPE (c);
+}
+
+/* FIXME: probably should make this into a property */
+GList *
+wocky_jingle_session_get_contents (WockyJingleSession *sess)
+{
+ WockyJingleSessionPrivate *priv = sess->priv;
+
+ return g_list_concat (g_hash_table_get_values (priv->initiator_contents),
+ g_hash_table_get_values (priv->responder_contents));
+}
+
+const gchar *
+wocky_jingle_session_get_peer_resource (WockyJingleSession *sess)
+{
+ return sess->priv->peer_resource;
+}
+
+const gchar *
+wocky_jingle_session_get_initiator (WockyJingleSession *sess)
+{
+ return sess->priv->initiator;
+}
+
+const gchar *
+wocky_jingle_session_get_sid (WockyJingleSession *sess)
+{
+ return sess->priv->sid;
+}
+
+static void
+content_ready_cb (WockyJingleContent *c, gpointer user_data)
+{
+ WockyJingleSession *sess = WOCKY_JINGLE_SESSION (user_data);
+ const gchar *disposition;
+
+ DEBUG ("called");
+
+ disposition = wocky_jingle_content_get_disposition (c);
+ /* This assertion is actually safe, because 'ready' is only emitted by
+ * contents with disposition "session". But this is crazy.
+ */
+ g_assert (!wocky_strdiff (disposition, "session"));
+
+ try_session_initiate_or_accept (sess);
+}
+
+static void
+wocky_jingle_session_send_rtp_info (WockyJingleSession *sess,
+ const gchar *name)
+{
+ WockyStanza *message;
+ WockyNode *jingle;
+
+ if (!wocky_jingle_session_defines_action (sess, WOCKY_JINGLE_ACTION_SESSION_INFO))
+ {
+ DEBUG ("Not sending <%s/>; not using modern Jingle", name);
+ return;
+ }
+
+ message = wocky_jingle_session_new_message (sess,
+ WOCKY_JINGLE_ACTION_SESSION_INFO, &jingle);
+ wocky_node_add_child_ns_q (jingle, name,
+ g_quark_from_static_string (WOCKY_XMPP_NS_JINGLE_RTP_INFO));
+
+ /* This is just informational, so ignoring the reply. */
+ wocky_jingle_session_send (sess, message);
+}
+
+static void
+wocky_jingle_session_send_held (WockyJingleSession *sess)
+{
+ const gchar *s = (sess->priv->local_hold ? "hold" : "unhold");
+
+ wocky_jingle_session_send_rtp_info (sess, s);
+}
+
+void
+wocky_jingle_session_set_local_hold (WockyJingleSession *sess,
+ gboolean held)
+{
+ g_object_set (sess, "local-hold", held, NULL);
+}
+
+gboolean
+wocky_jingle_session_get_remote_hold (WockyJingleSession *sess)
+{
+ g_assert (WOCKY_IS_JINGLE_SESSION (sess));
+
+ return sess->priv->remote_hold;
+}
+
+gboolean
+wocky_jingle_session_get_remote_ringing (WockyJingleSession *sess)
+{
+ g_assert (WOCKY_IS_JINGLE_SESSION (sess));
+
+ return sess->priv->remote_ringing;
+}
+
+gboolean
+wocky_jingle_session_can_modify_contents (WockyJingleSession *sess)
+{
+ return !WOCKY_JINGLE_DIALECT_IS_GOOGLE (sess->priv->dialect) &&
+ !wocky_jingle_session_peer_has_cap (sess, WOCKY_QUIRK_GOOGLE_WEBMAIL_CLIENT);
+}
+
+WockyJingleDialect
+wocky_jingle_session_get_dialect (WockyJingleSession *sess)
+{
+ return sess->priv->dialect;
+}
+
+WockyContact *
+wocky_jingle_session_get_peer_contact (WockyJingleSession *self)
+{
+ return self->priv->peer_contact;
+}
+
+/*
+ * wocky_jingle_session_get_peer_jid:
+ * @sess: a jingle session
+ *
+ * Returns: the full JID of the remote contact.
+ */
+const gchar *
+wocky_jingle_session_get_peer_jid (WockyJingleSession *sess)
+{
+ return sess->priv->peer_jid;
+}
+
+WockyJingleFactory *
+wocky_jingle_session_get_factory (WockyJingleSession *self)
+{
+ return self->priv->jingle_factory;
+}
+
+WockyPorter *
+wocky_jingle_session_get_porter (WockyJingleSession *self)
+{
+ return self->priv->porter;
+}
diff --git a/wocky/wocky-jingle-session.h b/wocky/wocky-jingle-session.h
new file mode 100644
index 0000000..9a49a37
--- /dev/null
+++ b/wocky/wocky-jingle-session.h
@@ -0,0 +1,137 @@
+/*
+ * wocky-jingle-session.h - Header for WockyJingleSession
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#if !defined (WOCKY_H_INSIDE) && !defined (WOCKY_COMPILATION)
+# error "Only <wocky/wocky.h> can be included directly."
+#endif
+
+#ifndef __JINGLE_SESSION_H__
+#define __JINGLE_SESSION_H__
+
+#include <glib-object.h>
+
+#include "wocky-jingle-content.h"
+#include "wocky-jingle-factory.h"
+#include "wocky-jingle-types.h"
+
+G_BEGIN_DECLS
+
+typedef struct _WockyJingleSessionClass WockyJingleSessionClass;
+
+GType wocky_jingle_session_get_type (void);
+
+/* TYPE MACROS */
+#define WOCKY_TYPE_JINGLE_SESSION \
+ (wocky_jingle_session_get_type ())
+#define WOCKY_JINGLE_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), WOCKY_TYPE_JINGLE_SESSION, \
+ WockyJingleSession))
+#define WOCKY_JINGLE_SESSION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), WOCKY_TYPE_JINGLE_SESSION, \
+ WockyJingleSessionClass))
+#define WOCKY_IS_JINGLE_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), WOCKY_TYPE_JINGLE_SESSION))
+#define WOCKY_IS_JINGLE_SESSION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), WOCKY_TYPE_JINGLE_SESSION))
+#define WOCKY_JINGLE_SESSION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), WOCKY_TYPE_JINGLE_SESSION, \
+ WockyJingleSessionClass))
+
+struct _WockyJingleSessionClass {
+ GObjectClass parent_class;
+};
+
+typedef struct _WockyJingleSessionPrivate WockyJingleSessionPrivate;
+
+struct _WockyJingleSession {
+ GObject parent;
+ WockyJingleSessionPrivate *priv;
+};
+
+WockyJingleSession *wocky_jingle_session_new (
+ WockyJingleFactory *factory,
+ WockyPorter *porter,
+ const gchar *session_id,
+ gboolean local_initiator,
+ WockyContact *peer,
+ WockyJingleDialect dialect,
+ gboolean local_hold);
+
+const gchar * wocky_jingle_session_detect (WockyStanza *stanza,
+ WockyJingleAction *action, WockyJingleDialect *dialect);
+gboolean wocky_jingle_session_parse (WockyJingleSession *sess,
+ WockyJingleAction action, WockyStanza *stanza, GError **error);
+WockyStanza *wocky_jingle_session_new_message (WockyJingleSession *sess,
+ WockyJingleAction action, WockyNode **sess_node);
+
+void wocky_jingle_session_accept (WockyJingleSession *sess);
+gboolean wocky_jingle_session_terminate (WockyJingleSession *sess,
+ WockyJingleReason reason,
+ const gchar *text,
+ GError **error);
+void wocky_jingle_session_remove_content (WockyJingleSession *sess,
+ WockyJingleContent *c);
+
+WockyJingleContent *
+wocky_jingle_session_add_content (WockyJingleSession *sess,
+ WockyJingleMediaType mtype,
+ WockyJingleContentSenders senders,
+ const char *name,
+ const gchar *content_ns,
+ const gchar *transport_ns);
+
+GType wocky_jingle_session_get_content_type (WockyJingleSession *);
+GList *wocky_jingle_session_get_contents (WockyJingleSession *sess);
+const gchar *wocky_jingle_session_get_peer_resource (
+ WockyJingleSession *sess);
+const gchar *wocky_jingle_session_get_initiator (
+ WockyJingleSession *sess);
+const gchar *wocky_jingle_session_get_sid (WockyJingleSession *sess);
+WockyJingleDialect wocky_jingle_session_get_dialect (WockyJingleSession *sess);
+
+gboolean wocky_jingle_session_can_modify_contents (WockyJingleSession *sess);
+gboolean wocky_jingle_session_peer_has_cap (
+ WockyJingleSession *self,
+ const gchar *cap_or_quirk);
+
+void wocky_jingle_session_send (
+ WockyJingleSession *sess,
+ WockyStanza *stanza);
+
+void wocky_jingle_session_set_local_hold (WockyJingleSession *sess,
+ gboolean held);
+
+gboolean wocky_jingle_session_get_remote_hold (WockyJingleSession *sess);
+
+gboolean wocky_jingle_session_get_remote_ringing (WockyJingleSession *sess);
+
+gboolean wocky_jingle_session_defines_action (WockyJingleSession *sess,
+ WockyJingleAction action);
+
+WockyContact *wocky_jingle_session_get_peer_contact (WockyJingleSession *self);
+const gchar *wocky_jingle_session_get_peer_jid (WockyJingleSession *sess);
+
+const gchar *wocky_jingle_session_get_reason_name (WockyJingleReason reason);
+
+WockyJingleFactory *wocky_jingle_session_get_factory (WockyJingleSession *self);
+WockyPorter *wocky_jingle_session_get_porter (WockyJingleSession *self);
+
+G_END_DECLS
+
+#endif /* __JINGLE_SESSION_H__ */
+
diff --git a/wocky/wocky-jingle-transport-google.c b/wocky/wocky-jingle-transport-google.c
new file mode 100644
index 0000000..ed6a0cc
--- /dev/null
+++ b/wocky/wocky-jingle-transport-google.c
@@ -0,0 +1,642 @@
+/*
+ * wocky-jingle-transport-google.c - Source for WockyJingleTransportGoogle
+ *
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "wocky-jingle-transport-google.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#define WOCKY_DEBUG_FLAG WOCKY_DEBUG_JINGLE
+
+#include "wocky-debug-internal.h"
+#include "wocky-jingle-content.h"
+#include "wocky-jingle-factory.h"
+#include "wocky-jingle-session.h"
+#include "wocky-namespaces.h"
+#include "wocky-utils.h"
+
+static void
+transport_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (WockyJingleTransportGoogle,
+ wocky_jingle_transport_google, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (WOCKY_TYPE_JINGLE_TRANSPORT_IFACE,
+ transport_iface_init));
+
+/* signal enum */
+enum
+{
+ NEW_CANDIDATES,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum
+{
+ PROP_CONTENT = 1,
+ PROP_TRANSPORT_NS,
+ PROP_STATE,
+ LAST_PROPERTY
+};
+
+struct _WockyJingleTransportGooglePrivate
+{
+ WockyJingleContent *content;
+ WockyJingleTransportState state;
+ gchar *transport_ns;
+
+ /* Component names or jingle-share transport 'channels'
+ g_strdup'd component name => GINT_TO_POINTER (component id) */
+ GHashTable *component_names;
+
+ GList *local_candidates;
+
+ /* A pointer into "local_candidates" list to mark the
+ * candidates that are still not transmitted, or NULL
+ * if all of them are transmitted. */
+
+ GList *pending_candidates;
+ GList *remote_candidates;
+ gboolean dispose_has_run;
+};
+
+static void
+wocky_jingle_transport_google_init (WockyJingleTransportGoogle *obj)
+{
+ WockyJingleTransportGooglePrivate *priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (obj, WOCKY_TYPE_JINGLE_TRANSPORT_GOOGLE,
+ WockyJingleTransportGooglePrivate);
+ obj->priv = priv;
+
+ priv->component_names = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+
+ priv->dispose_has_run = FALSE;
+}
+
+static void
+wocky_jingle_transport_google_dispose (GObject *object)
+{
+ WockyJingleTransportGoogle *trans = WOCKY_JINGLE_TRANSPORT_GOOGLE (object);
+ WockyJingleTransportGooglePrivate *priv = trans->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ DEBUG ("dispose called");
+ priv->dispose_has_run = TRUE;
+
+ g_hash_table_unref (priv->component_names);
+ priv->component_names = NULL;
+
+ jingle_transport_free_candidates (priv->remote_candidates);
+ priv->remote_candidates = NULL;
+
+ jingle_transport_free_candidates (priv->local_candidates);
+ priv->local_candidates = NULL;
+
+ g_free (priv->transport_ns);
+ priv->transport_ns = NULL;
+
+ if (G_OBJECT_CLASS (wocky_jingle_transport_google_parent_class)->dispose)
+ G_OBJECT_CLASS (wocky_jingle_transport_google_parent_class)->dispose (object);
+}
+
+static void
+wocky_jingle_transport_google_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleTransportGoogle *trans = WOCKY_JINGLE_TRANSPORT_GOOGLE (object);
+ WockyJingleTransportGooglePrivate *priv = trans->priv;
+
+ switch (property_id) {
+ case PROP_CONTENT:
+ g_value_set_object (value, priv->content);
+ break;
+ case PROP_TRANSPORT_NS:
+ g_value_set_string (value, priv->transport_ns);
+ break;
+ case PROP_STATE:
+ g_value_set_uint (value, priv->state);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_jingle_transport_google_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleTransportGoogle *trans = WOCKY_JINGLE_TRANSPORT_GOOGLE (object);
+ WockyJingleTransportGooglePrivate *priv = trans->priv;
+
+ switch (property_id) {
+ case PROP_CONTENT:
+ priv->content = g_value_get_object (value);
+ break;
+ case PROP_TRANSPORT_NS:
+ g_free (priv->transport_ns);
+ priv->transport_ns = g_value_dup_string (value);
+ break;
+ case PROP_STATE:
+ priv->state = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_jingle_transport_google_class_init (WockyJingleTransportGoogleClass *cls)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (cls);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (cls, sizeof (WockyJingleTransportGooglePrivate));
+
+ object_class->get_property = wocky_jingle_transport_google_get_property;
+ object_class->set_property = wocky_jingle_transport_google_set_property;
+ object_class->dispose = wocky_jingle_transport_google_dispose;
+
+ /* property definitions */
+ param_spec = g_param_spec_object ("content", "WockyJingleContent object",
+ "Jingle content object using this transport.",
+ WOCKY_TYPE_JINGLE_CONTENT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONTENT, param_spec);
+
+ param_spec = g_param_spec_string ("transport-ns", "Transport namespace",
+ "Namespace identifying the transport type.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_TRANSPORT_NS, param_spec);
+
+ param_spec = g_param_spec_uint ("state",
+ "Connection state for the transport.",
+ "Enum specifying the connection state of the transport.",
+ WOCKY_JINGLE_TRANSPORT_STATE_DISCONNECTED,
+ WOCKY_JINGLE_TRANSPORT_STATE_CONNECTED,
+ WOCKY_JINGLE_TRANSPORT_STATE_DISCONNECTED,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_STATE, param_spec);
+
+ /* signal definitions */
+ signals[NEW_CANDIDATES] = g_signal_new (
+ "new-candidates",
+ G_TYPE_FROM_CLASS (cls),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+}
+
+static void
+parse_candidates (WockyJingleTransportIface *obj,
+ WockyNode *transport_node, GError **error)
+{
+ WockyJingleTransportGoogle *t = WOCKY_JINGLE_TRANSPORT_GOOGLE (obj);
+ WockyJingleTransportGooglePrivate *priv = t->priv;
+ GList *candidates = NULL;
+ WockyNodeIter i;
+ WockyNode *node;
+
+ wocky_node_iter_init (&i, transport_node, "candidate", NULL);
+ while (wocky_node_iter_next (&i, &node))
+ {
+ const gchar *name, *address, *user, *pass, *str;
+ guint port, net, gen, component;
+ int pref;
+ WockyJingleTransportProtocol proto;
+ WockyJingleCandidateType ctype;
+ WockyJingleCandidate *c;
+
+ name = wocky_node_get_attribute (node, "name");
+ if (name == NULL)
+ break;
+
+ if (!g_hash_table_lookup_extended (priv->component_names, name,
+ NULL, NULL))
+ {
+ DEBUG ("component name %s unknown to this transport", name);
+ continue;
+ }
+
+ component = GPOINTER_TO_INT (g_hash_table_lookup (priv->component_names,
+ name));
+ address = wocky_node_get_attribute (node, "address");
+ if (address == NULL)
+ break;
+
+ str = wocky_node_get_attribute (node, "port");
+ if (str == NULL)
+ break;
+ port = atoi (str);
+
+ str = wocky_node_get_attribute (node, "protocol");
+ if (str == NULL)
+ break;
+
+ if (!wocky_strdiff (str, "udp"))
+ {
+ proto = WOCKY_JINGLE_TRANSPORT_PROTOCOL_UDP;
+ }
+ else if (!wocky_strdiff (str, "tcp"))
+ {
+ /* candiates on port 443 must be "ssltcp" */
+ if (port == 443)
+ break;
+
+ proto = WOCKY_JINGLE_TRANSPORT_PROTOCOL_TCP;
+ }
+ else if (!wocky_strdiff (str, "ssltcp"))
+ {
+ /* "ssltcp" must use port 443 */
+ if (port != 443)
+ break;
+
+ /* we really don't care about "ssltcp" otherwise */
+ proto = WOCKY_JINGLE_TRANSPORT_PROTOCOL_TCP;
+ }
+ else
+ {
+ /* unknown protocol */
+ DEBUG ("unknown protocol: %s", str);
+ break;
+ }
+
+ str = wocky_node_get_attribute (node, "preference");
+ if (str == NULL)
+ break;
+
+ pref = g_ascii_strtod (str, NULL) * 65536;
+
+ str = wocky_node_get_attribute (node, "type");
+ if (str == NULL)
+ break;
+
+ if (!wocky_strdiff (str, "local"))
+ {
+ ctype = WOCKY_JINGLE_CANDIDATE_TYPE_LOCAL;
+ }
+ else if (!wocky_strdiff (str, "stun"))
+ {
+ ctype = WOCKY_JINGLE_CANDIDATE_TYPE_STUN;
+ }
+ else if (!wocky_strdiff (str, "relay"))
+ {
+ ctype = WOCKY_JINGLE_CANDIDATE_TYPE_RELAY;
+ }
+ else
+ {
+ /* unknown candidate type */
+ DEBUG ("unknown candidate type: %s", str);
+ break;
+ }
+
+ user = wocky_node_get_attribute (node, "username");
+ if (user == NULL)
+ break;
+
+ pass = wocky_node_get_attribute (node, "password");
+ if (pass == NULL)
+ break;
+
+ str = wocky_node_get_attribute (node, "network");
+ if (str == NULL)
+ break;
+ net = atoi (str);
+
+ str = wocky_node_get_attribute (node, "generation");
+ if (str == NULL)
+ break;
+ gen = atoi (str);
+
+ str = wocky_node_get_attribute (node, "component");
+ if (str != NULL)
+ component = atoi (str);
+
+ c = wocky_jingle_candidate_new (proto, ctype, NULL, component,
+ address, port, gen, pref, user, pass, net);
+
+ candidates = g_list_append (candidates, c);
+ }
+
+ if (wocky_node_iter_next (&i, NULL))
+ {
+ DEBUG ("not all nodes were processed, reporting error");
+ /* rollback these */
+ jingle_transport_free_candidates (candidates);
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "invalid candidate");
+ return;
+ }
+
+ DEBUG ("emitting %d new remote candidates", g_list_length (candidates));
+
+ g_signal_emit (obj, signals[NEW_CANDIDATES], 0, candidates);
+
+ /* append them to the known remote candidates */
+ priv->remote_candidates = g_list_concat (priv->remote_candidates, candidates);
+}
+
+static void
+transmit_candidates (WockyJingleTransportGoogle *transport,
+ const gchar *name,
+ GList *candidates)
+{
+ WockyJingleTransportGooglePrivate *priv = transport->priv;
+ GList *li;
+ WockyStanza *msg;
+ WockyNode *trans_node, *sess_node;
+
+ if (candidates == NULL)
+ return;
+
+ msg = wocky_jingle_session_new_message (priv->content->session,
+ WOCKY_JINGLE_ACTION_TRANSPORT_INFO, &sess_node);
+
+ wocky_jingle_content_produce_node (priv->content, sess_node, FALSE, TRUE,
+ &trans_node);
+
+ for (li = candidates; li; li = li->next)
+ {
+ WockyJingleCandidate *c = (WockyJingleCandidate *) li->data;
+ gchar port_str[16], pref_str[16], comp_str[16], *type_str, *proto_str;
+ WockyNode *cnode;
+
+ sprintf (port_str, "%d", c->port);
+ sprintf (pref_str, "%lf", c->preference / 65536.0);
+ sprintf (comp_str, "%d", c->component);
+
+ switch (c->type) {
+ case WOCKY_JINGLE_CANDIDATE_TYPE_LOCAL:
+ type_str = "local";
+ break;
+ case WOCKY_JINGLE_CANDIDATE_TYPE_STUN:
+ type_str = "stun";
+ break;
+ case WOCKY_JINGLE_CANDIDATE_TYPE_RELAY:
+ type_str = "relay";
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ switch (c->protocol) {
+ case WOCKY_JINGLE_TRANSPORT_PROTOCOL_UDP:
+ proto_str = "udp";
+ break;
+ case WOCKY_JINGLE_TRANSPORT_PROTOCOL_TCP:
+ if ((c->port == 443) && (c->type == WOCKY_JINGLE_CANDIDATE_TYPE_RELAY))
+ proto_str = "ssltcp";
+ else
+ proto_str = "tcp";
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ cnode = wocky_node_add_child (trans_node, "candidate");
+ wocky_node_set_attributes (cnode,
+ "address", c->address,
+ "port", port_str,
+ "username", c->username,
+ "password", c->password != NULL ? c->password : "",
+ "preference", pref_str,
+ "protocol", proto_str,
+ "type", type_str,
+ "component", comp_str,
+ "network", "0",
+ "generation", "0",
+ NULL);
+
+ wocky_node_set_attribute (cnode, "name", name);
+ }
+
+ wocky_porter_send_iq_async (
+ wocky_jingle_session_get_porter (priv->content->session), msg,
+ NULL, NULL, NULL);
+ g_object_unref (msg);
+}
+
+/* Groups @candidates into rtp and rtcp and sends each group in its own
+ * transport-info. This works around old Gabble, which rejected transport-info
+ * stanzas containing non-rtp candidates.
+ */
+static void
+group_and_transmit_candidates (WockyJingleTransportGoogle *transport,
+ GList *candidates)
+{
+ WockyJingleTransportGooglePrivate *priv = transport->priv;
+ GList *all_candidates = NULL;
+ GList *li;
+ GList *cands;
+
+ for (li = candidates; li != NULL; li = g_list_next (li))
+ {
+ WockyJingleCandidate *c = li->data;
+
+ for (cands = all_candidates; cands != NULL; cands = g_list_next (cands))
+ {
+ WockyJingleCandidate *c2 = ((GList *) cands->data)->data;
+
+ if (c->component == c2->component)
+ {
+ break;
+ }
+ }
+ if (cands == NULL)
+ {
+ all_candidates = g_list_prepend (all_candidates, NULL);
+ cands = all_candidates;
+ }
+
+ cands->data = g_list_prepend (cands->data, c);
+ }
+
+ for (cands = all_candidates; cands != NULL; cands = g_list_next (cands))
+ {
+ GHashTableIter iter;
+ gpointer key, value;
+ gchar *name = NULL;
+ WockyJingleCandidate *c = ((GList *) cands->data)->data;
+
+ g_hash_table_iter_init (&iter, priv->component_names);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ if (GPOINTER_TO_INT (value) == c->component)
+ {
+ name = key;
+ break;
+ }
+ }
+ if (name)
+ {
+ transmit_candidates (transport, name, cands->data);
+ }
+ else
+ {
+ DEBUG ("Ignoring unknown component %d", c->component);
+ }
+ g_list_free (cands->data);
+ }
+
+ g_list_free (all_candidates);
+}
+
+/* Takes in a list of slice-allocated WockyJingleCandidate structs */
+static void
+new_local_candidates (WockyJingleTransportIface *obj, GList *new_candidates)
+{
+ WockyJingleTransportGoogle *transport =
+ WOCKY_JINGLE_TRANSPORT_GOOGLE (obj);
+ WockyJingleTransportGooglePrivate *priv = transport->priv;
+
+ priv->local_candidates = g_list_concat (priv->local_candidates,
+ new_candidates);
+
+ /* If all previous candidates have been signalled, set the new
+ * ones as pending. If there are existing pending candidates,
+ * the new ones will just be appended to that list. */
+ if (priv->pending_candidates == NULL)
+ priv->pending_candidates = new_candidates;
+}
+
+static void
+send_candidates (WockyJingleTransportIface *obj, gboolean all)
+{
+ WockyJingleTransportGoogle *transport =
+ WOCKY_JINGLE_TRANSPORT_GOOGLE (obj);
+ WockyJingleTransportGooglePrivate *priv = transport->priv;
+
+ if (all)
+ {
+ /* for gtalk3, we might have to retransmit everything */
+ group_and_transmit_candidates (transport, priv->local_candidates);
+ priv->pending_candidates = NULL;
+ }
+ else
+ {
+ /* If the content became ready after we wanted to transmit
+ * these originally, we are called to transmit when it them */
+ if (priv->pending_candidates != NULL)
+ {
+ group_and_transmit_candidates (transport, priv->pending_candidates);
+ priv->pending_candidates = NULL;
+ }
+ }
+}
+
+static GList *
+get_local_candidates (WockyJingleTransportIface *iface)
+{
+ WockyJingleTransportGoogle *transport =
+ WOCKY_JINGLE_TRANSPORT_GOOGLE (iface);
+ WockyJingleTransportGooglePrivate *priv = transport->priv;
+
+ return priv->local_candidates;
+}
+
+static GList *
+get_remote_candidates (WockyJingleTransportIface *iface)
+{
+ WockyJingleTransportGoogle *transport =
+ WOCKY_JINGLE_TRANSPORT_GOOGLE (iface);
+ WockyJingleTransportGooglePrivate *priv = transport->priv;
+
+ return priv->remote_candidates;
+}
+
+static WockyJingleTransportType
+get_transport_type (void)
+{
+ return JINGLE_TRANSPORT_GOOGLE_P2P;
+}
+
+static void
+transport_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ WockyJingleTransportIfaceClass *klass = (WockyJingleTransportIfaceClass *) g_iface;
+
+ klass->parse_candidates = parse_candidates;
+
+ klass->new_local_candidates = new_local_candidates;
+ /* Not implementing inject_candidates: gtalk-p2p candidates are always sent
+ * in transport-info or equivalent.
+ */
+ klass->send_candidates = send_candidates;
+
+ klass->get_remote_candidates = get_remote_candidates;
+ klass->get_local_candidates = get_local_candidates;
+ klass->get_transport_type = get_transport_type;
+}
+
+/* Returns FALSE if the component name already exists */
+gboolean
+jingle_transport_google_set_component_name (
+ WockyJingleTransportGoogle *transport,
+ const gchar *name, guint component_id)
+{
+ WockyJingleTransportGooglePrivate *priv = transport->priv;
+
+ if (g_hash_table_lookup_extended (priv->component_names, name, NULL, NULL))
+ return FALSE;
+
+ g_hash_table_insert (priv->component_names, g_strdup (name),
+ GINT_TO_POINTER (component_id));
+
+ return TRUE;
+}
+
+void
+jingle_transport_google_register (WockyJingleFactory *factory)
+{
+ /* GTalk libjingle0.3 dialect */
+ wocky_jingle_factory_register_transport (factory, "",
+ WOCKY_TYPE_JINGLE_TRANSPORT_GOOGLE);
+
+ /* GTalk libjingle0.4 dialect */
+ wocky_jingle_factory_register_transport (factory,
+ WOCKY_XMPP_NS_GOOGLE_TRANSPORT_P2P,
+ WOCKY_TYPE_JINGLE_TRANSPORT_GOOGLE);
+}
+
diff --git a/wocky/wocky-jingle-transport-google.h b/wocky/wocky-jingle-transport-google.h
new file mode 100644
index 0000000..aca6bb8
--- /dev/null
+++ b/wocky/wocky-jingle-transport-google.h
@@ -0,0 +1,73 @@
+/*
+ * wocky-jingle-transport-google.h - Header for WockyJingleTransportGoogle
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#if !defined (WOCKY_H_INSIDE) && !defined (WOCKY_COMPILATION)
+# error "Only <wocky/wocky.h> can be included directly."
+#endif
+
+#ifndef __JINGLE_TRANSPORT_GOOGLE_H__
+#define __JINGLE_TRANSPORT_GOOGLE_H__
+
+#include <glib-object.h>
+
+#include "wocky-jingle-types.h"
+
+G_BEGIN_DECLS
+
+typedef struct _WockyJingleTransportGoogleClass WockyJingleTransportGoogleClass;
+
+GType wocky_jingle_transport_google_get_type (void);
+
+/* TYPE MACROS */
+#define WOCKY_TYPE_JINGLE_TRANSPORT_GOOGLE \
+ (wocky_jingle_transport_google_get_type ())
+#define WOCKY_JINGLE_TRANSPORT_GOOGLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), WOCKY_TYPE_JINGLE_TRANSPORT_GOOGLE, \
+ WockyJingleTransportGoogle))
+#define WOCKY_JINGLE_TRANSPORT_GOOGLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), WOCKY_TYPE_JINGLE_TRANSPORT_GOOGLE, \
+ WockyJingleTransportGoogleClass))
+#define WOCKY_IS_JINGLE_TRANSPORT_GOOGLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), WOCKY_TYPE_JINGLE_TRANSPORT_GOOGLE))
+#define WOCKY_IS_JINGLE_TRANSPORT_GOOGLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), WOCKY_TYPE_JINGLE_TRANSPORT_GOOGLE))
+#define WOCKY_JINGLE_TRANSPORT_GOOGLE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), WOCKY_TYPE_JINGLE_TRANSPORT_GOOGLE, \
+ WockyJingleTransportGoogleClass))
+
+struct _WockyJingleTransportGoogleClass {
+ GObjectClass parent_class;
+};
+
+typedef struct _WockyJingleTransportGooglePrivate WockyJingleTransportGooglePrivate;
+
+struct _WockyJingleTransportGoogle {
+ GObject parent;
+ WockyJingleTransportGooglePrivate *priv;
+};
+
+void jingle_transport_google_register (WockyJingleFactory *factory);
+
+gboolean jingle_transport_google_set_component_name (
+ WockyJingleTransportGoogle *transport,
+ const gchar *name, guint component_id);
+
+G_END_DECLS
+
+#endif /* __JINGLE_TRANSPORT_GOOGLE_H__ */
+
diff --git a/wocky/wocky-jingle-transport-iceudp.c b/wocky/wocky-jingle-transport-iceudp.c
new file mode 100644
index 0000000..ab4e0ef
--- /dev/null
+++ b/wocky/wocky-jingle-transport-iceudp.c
@@ -0,0 +1,618 @@
+/*
+ * wocky-jingle-transport-iceudp.c - Source for WockyJingleTransportIceUdp
+ *
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "wocky-jingle-transport-iceudp.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#define WOCKY_DEBUG_FLAG WOCKY_DEBUG_JINGLE
+
+#include "wocky-debug-internal.h"
+#include "wocky-jingle-content.h"
+#include "wocky-jingle-factory.h"
+#include "wocky-jingle-session.h"
+#include "wocky-namespaces.h"
+#include "wocky-utils.h"
+
+static void
+transport_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (WockyJingleTransportIceUdp,
+ wocky_jingle_transport_iceudp, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (WOCKY_TYPE_JINGLE_TRANSPORT_IFACE,
+ transport_iface_init));
+
+/* signal enum */
+enum
+{
+ NEW_CANDIDATES,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum
+{
+ PROP_CONTENT = 1,
+ PROP_TRANSPORT_NS,
+ PROP_STATE,
+ LAST_PROPERTY
+};
+
+struct _WockyJingleTransportIceUdpPrivate
+{
+ WockyJingleContent *content;
+ WockyJingleTransportState state;
+ gchar *transport_ns;
+
+ GList *local_candidates;
+
+ /* A pointer into "local_candidates" list to mark the
+ * candidates that are still not transmitted, or NULL
+ * if all of them are transmitted. */
+
+ GList *pending_candidates;
+ GList *remote_candidates;
+
+ gchar *ufrag;
+ gchar *pwd;
+
+ /* next ID to send with a candidate */
+ int id_sequence;
+
+ gboolean dispose_has_run;
+};
+
+static void
+wocky_jingle_transport_iceudp_init (WockyJingleTransportIceUdp *obj)
+{
+ WockyJingleTransportIceUdpPrivate *priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (obj, WOCKY_TYPE_JINGLE_TRANSPORT_ICEUDP,
+ WockyJingleTransportIceUdpPrivate);
+ obj->priv = priv;
+
+ priv->id_sequence = 1;
+ priv->dispose_has_run = FALSE;
+}
+
+static void
+wocky_jingle_transport_iceudp_dispose (GObject *object)
+{
+ WockyJingleTransportIceUdp *trans = WOCKY_JINGLE_TRANSPORT_ICEUDP (object);
+ WockyJingleTransportIceUdpPrivate *priv = trans->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ DEBUG ("dispose called");
+ priv->dispose_has_run = TRUE;
+
+ jingle_transport_free_candidates (priv->remote_candidates);
+ priv->remote_candidates = NULL;
+
+ jingle_transport_free_candidates (priv->local_candidates);
+ priv->local_candidates = NULL;
+
+ g_free (priv->transport_ns);
+ priv->transport_ns = NULL;
+
+ g_free (priv->ufrag);
+ priv->ufrag = NULL;
+
+ g_free (priv->pwd);
+ priv->pwd = NULL;
+
+ if (G_OBJECT_CLASS (wocky_jingle_transport_iceudp_parent_class)->dispose)
+ G_OBJECT_CLASS (wocky_jingle_transport_iceudp_parent_class)->dispose (object);
+}
+
+static void
+wocky_jingle_transport_iceudp_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleTransportIceUdp *trans = WOCKY_JINGLE_TRANSPORT_ICEUDP (object);
+ WockyJingleTransportIceUdpPrivate *priv = trans->priv;
+
+ switch (property_id) {
+ case PROP_CONTENT:
+ g_value_set_object (value, priv->content);
+ break;
+ case PROP_TRANSPORT_NS:
+ g_value_set_string (value, priv->transport_ns);
+ break;
+ case PROP_STATE:
+ g_value_set_uint (value, priv->state);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_jingle_transport_iceudp_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleTransportIceUdp *trans = WOCKY_JINGLE_TRANSPORT_ICEUDP (object);
+ WockyJingleTransportIceUdpPrivate *priv = trans->priv;
+
+ switch (property_id) {
+ case PROP_CONTENT:
+ priv->content = g_value_get_object (value);
+ break;
+ case PROP_TRANSPORT_NS:
+ g_free (priv->transport_ns);
+ priv->transport_ns = g_value_dup_string (value);
+ break;
+ case PROP_STATE:
+ priv->state = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_jingle_transport_iceudp_class_init (WockyJingleTransportIceUdpClass *cls)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (cls);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (cls, sizeof (WockyJingleTransportIceUdpPrivate));
+
+ object_class->get_property = wocky_jingle_transport_iceudp_get_property;
+ object_class->set_property = wocky_jingle_transport_iceudp_set_property;
+ object_class->dispose = wocky_jingle_transport_iceudp_dispose;
+
+ /* property definitions */
+ param_spec = g_param_spec_object ("content", "WockyJingleContent object",
+ "Jingle content object using this transport.",
+ WOCKY_TYPE_JINGLE_CONTENT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONTENT, param_spec);
+
+ param_spec = g_param_spec_string ("transport-ns", "Transport namespace",
+ "Namespace identifying the transport type.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_TRANSPORT_NS, param_spec);
+
+ param_spec = g_param_spec_uint ("state",
+ "Connection state for the transport.",
+ "Enum specifying the connection state of the transport.",
+ WOCKY_JINGLE_TRANSPORT_STATE_DISCONNECTED,
+ WOCKY_JINGLE_TRANSPORT_STATE_CONNECTED,
+ WOCKY_JINGLE_TRANSPORT_STATE_DISCONNECTED,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_STATE, param_spec);
+
+ /* signal definitions */
+ signals[NEW_CANDIDATES] = g_signal_new (
+ "new-candidates",
+ G_TYPE_FROM_CLASS (cls),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+}
+
+static void
+parse_candidates (WockyJingleTransportIface *obj,
+ WockyNode *transport_node, GError **error)
+{
+ WockyJingleTransportIceUdp *t = WOCKY_JINGLE_TRANSPORT_ICEUDP (obj);
+ WockyJingleTransportIceUdpPrivate *priv = t->priv;
+ gboolean node_contains_a_candidate = FALSE;
+ GList *candidates = NULL;
+ WockyNodeIter i;
+ WockyNode *node;
+
+ DEBUG ("called");
+
+ wocky_node_iter_init (&i, transport_node, "candidate", NULL);
+ while (wocky_node_iter_next (&i, &node))
+ {
+ const gchar *id, *address, *user, *pass, *str;
+ guint port, net, gen, component = 1;
+ gdouble pref;
+ WockyJingleTransportProtocol proto;
+ WockyJingleCandidateType ctype;
+ WockyJingleCandidate *c;
+
+ node_contains_a_candidate = TRUE;
+
+ id = wocky_node_get_attribute (node, "foundation");
+ if (id == NULL)
+ {
+ DEBUG ("candidate doesn't contain foundation");
+ continue;
+ }
+
+ address = wocky_node_get_attribute (node, "ip");
+ if (address == NULL)
+ {
+ DEBUG ("candidate doesn't contain ip");
+ continue;
+ }
+
+ str = wocky_node_get_attribute (node, "port");
+ if (str == NULL)
+ {
+ DEBUG ("candidate doesn't contain port");
+ continue;
+ }
+ port = atoi (str);
+
+ str = wocky_node_get_attribute (node, "protocol");
+ if (str == NULL)
+ {
+ DEBUG ("candidate doesn't contain protocol");
+ continue;
+ }
+
+ if (!wocky_strdiff (str, "udp"))
+ {
+ proto = WOCKY_JINGLE_TRANSPORT_PROTOCOL_UDP;
+ }
+ else
+ {
+ /* unknown protocol */
+ DEBUG ("unknown protocol: %s", str);
+ continue;
+ }
+
+ str = wocky_node_get_attribute (node, "priority");
+ if (str == NULL)
+ {
+ DEBUG ("candidate doesn't contain priority");
+ continue;
+ }
+ pref = g_ascii_strtod (str, NULL);
+
+ str = wocky_node_get_attribute (node, "type");
+ if (str == NULL)
+ {
+ DEBUG ("candidate doesn't contain type");
+ continue;
+ }
+
+ if (!wocky_strdiff (str, "host"))
+ {
+ ctype = WOCKY_JINGLE_CANDIDATE_TYPE_LOCAL;
+ }
+ else if (!wocky_strdiff (str, "srflx") || !wocky_strdiff (str, "prflx"))
+ {
+ /* FIXME Strictly speaking a prflx candidate should be a different
+ * type, but the TP spec has now way to distinguish and it doesn't
+ * matter much anyway.. */
+ ctype = WOCKY_JINGLE_CANDIDATE_TYPE_STUN;
+ }
+ else if (!wocky_strdiff (str, "relay"))
+ {
+ ctype = WOCKY_JINGLE_CANDIDATE_TYPE_RELAY;
+ }
+ else
+ {
+ /* unknown candidate type */
+ DEBUG ("unknown candidate type: %s", str);
+ continue;
+ }
+
+ user = wocky_node_get_attribute (transport_node, "ufrag");
+ if (user == NULL)
+ {
+ DEBUG ("transport doesn't contain ufrag");
+ continue;
+ }
+
+ pass = wocky_node_get_attribute (transport_node, "pwd");
+ if (pass == NULL)
+ {
+ DEBUG ("transport doesn't contain pwd");
+ continue;
+ }
+
+ str = wocky_node_get_attribute (node, "network");
+ if (str == NULL)
+ {
+ DEBUG ("candidate doesn't contain network");
+ continue;
+ }
+ net = atoi (str);
+
+ str = wocky_node_get_attribute (node, "generation");
+ if (str == NULL)
+ {
+ DEBUG ("candidate doesn't contain generation");
+ continue;
+ }
+ gen = atoi (str);
+
+ str = wocky_node_get_attribute (node, "component");
+ if (str == NULL)
+ {
+ DEBUG ("candidate doesn't contain component");
+ continue;
+ }
+ component = atoi (str);
+
+ if (priv->ufrag == NULL || strcmp (priv->ufrag, user))
+ {
+ g_free (priv->ufrag);
+ priv->ufrag = g_strdup (user);
+ }
+
+ if (priv->pwd == NULL || strcmp (priv->pwd, pass))
+ {
+ g_free (priv->pwd);
+ priv->pwd = g_strdup (pass);
+ }
+
+ c = wocky_jingle_candidate_new (proto, ctype, id, component,
+ address, port, gen, pref, user, pass, net);
+
+ candidates = g_list_append (candidates, c);
+ }
+
+ if (candidates == NULL)
+ {
+ if (node_contains_a_candidate)
+ {
+ DEBUG_NODE (transport_node,
+ "couldn't parse any of the given candidates");
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "could not parse any of the given candidates");
+ }
+ else
+ {
+ DEBUG ("no candidates in this stanza");
+ }
+ }
+ else
+ {
+ DEBUG ("emitting %d new remote candidates", g_list_length (candidates));
+
+ g_signal_emit (obj, signals[NEW_CANDIDATES], 0, candidates);
+
+ priv->remote_candidates = g_list_concat (priv->remote_candidates,
+ candidates);
+ }
+}
+
+static void
+inject_candidates (WockyJingleTransportIface *obj,
+ WockyNode *transport_node)
+{
+ WockyJingleTransportIceUdp *self = WOCKY_JINGLE_TRANSPORT_ICEUDP (obj);
+ WockyJingleTransportIceUdpPrivate *priv = self->priv;
+ const gchar *username = NULL;
+
+ for (; priv->pending_candidates != NULL;
+ priv->pending_candidates = priv->pending_candidates->next)
+ {
+ WockyJingleCandidate *c = (WockyJingleCandidate *) priv->pending_candidates->data;
+ gchar port_str[16], pref_str[16], comp_str[16], id_str[16],
+ *type_str, *proto_str;
+ WockyNode *cnode;
+
+ if (username == NULL)
+ {
+ username = c->username;
+ }
+ else if (wocky_strdiff (username, c->username))
+ {
+ DEBUG ("found a candidate with a different username (%s not %s); "
+ "will send in a separate batch", c->username, username);
+ break;
+ }
+
+ sprintf (pref_str, "%d", c->preference);
+ sprintf (port_str, "%d", c->port);
+ sprintf (comp_str, "%d", c->component);
+ sprintf (id_str, "%d", priv->id_sequence++);
+
+ switch (c->type) {
+ case WOCKY_JINGLE_CANDIDATE_TYPE_LOCAL:
+ type_str = "host";
+ break;
+ case WOCKY_JINGLE_CANDIDATE_TYPE_STUN:
+ type_str = "srflx";
+ break;
+ case WOCKY_JINGLE_CANDIDATE_TYPE_RELAY:
+ type_str = "relay";
+ break;
+ default:
+ DEBUG ("skipping candidate with unknown type %u", c->type);
+ continue;
+ }
+
+ switch (c->protocol) {
+ case WOCKY_JINGLE_TRANSPORT_PROTOCOL_UDP:
+ proto_str = "udp";
+ break;
+ case WOCKY_JINGLE_TRANSPORT_PROTOCOL_TCP:
+ DEBUG ("ignoring TCP candidate");
+ continue;
+ default:
+ DEBUG ("skipping candidate with unknown protocol %u", c->protocol);
+ continue;
+ }
+
+ wocky_node_set_attributes (transport_node,
+ "ufrag", c->username,
+ "pwd", c->password,
+ NULL);
+
+ cnode = wocky_node_add_child (transport_node, "candidate");
+ wocky_node_set_attributes (cnode,
+ "ip", c->address,
+ "port", port_str,
+ "priority", pref_str,
+ "protocol", proto_str,
+ "type", type_str,
+ "component", comp_str,
+ "foundation", c->id,
+ "id", id_str,
+ "network", "0",
+ "generation", "0",
+ NULL);
+ }
+}
+
+/* We never have to retransmit candidates we've already sent, so we ignore
+ * @all.
+ */
+static void
+send_candidates (WockyJingleTransportIface *iface,
+ gboolean all G_GNUC_UNUSED)
+{
+ WockyJingleTransportIceUdp *self = WOCKY_JINGLE_TRANSPORT_ICEUDP (iface);
+ WockyJingleTransportIceUdpPrivate *priv = self->priv;
+
+ while (priv->pending_candidates != NULL)
+ {
+ WockyNode *trans_node, *sess_node;
+ WockyStanza *msg;
+
+ msg = wocky_jingle_session_new_message (priv->content->session,
+ WOCKY_JINGLE_ACTION_TRANSPORT_INFO, &sess_node);
+
+ wocky_jingle_content_produce_node (priv->content, sess_node, FALSE,
+ TRUE, &trans_node);
+ inject_candidates (iface, trans_node);
+
+ wocky_porter_send_iq_async (
+ wocky_jingle_session_get_porter (priv->content->session), msg,
+ NULL, NULL, NULL);
+ g_object_unref (msg);
+ }
+
+ DEBUG ("sent all pending candidates");
+}
+
+/* Takes in a list of slice-allocated WockyJingleCandidate structs */
+static void
+new_local_candidates (WockyJingleTransportIface *obj, GList *new_candidates)
+{
+ WockyJingleTransportIceUdp *transport =
+ WOCKY_JINGLE_TRANSPORT_ICEUDP (obj);
+ WockyJingleTransportIceUdpPrivate *priv = transport->priv;
+
+ priv->local_candidates = g_list_concat (priv->local_candidates,
+ new_candidates);
+
+ /* If all previous candidates have been signalled, set the new
+ * ones as pending. If there are existing pending candidates,
+ * the new ones will just be appended to that list. */
+ if (priv->pending_candidates == NULL)
+ priv->pending_candidates = new_candidates;
+}
+
+static GList *
+get_remote_candidates (WockyJingleTransportIface *iface)
+{
+ WockyJingleTransportIceUdp *transport =
+ WOCKY_JINGLE_TRANSPORT_ICEUDP (iface);
+ WockyJingleTransportIceUdpPrivate *priv = transport->priv;
+
+ return priv->remote_candidates;
+}
+
+static GList *
+get_local_candidates (WockyJingleTransportIface *iface)
+{
+ WockyJingleTransportIceUdp *transport =
+ WOCKY_JINGLE_TRANSPORT_ICEUDP (iface);
+ WockyJingleTransportIceUdpPrivate *priv = transport->priv;
+
+ return priv->local_candidates;
+}
+
+static WockyJingleTransportType
+get_transport_type (void)
+{
+ return JINGLE_TRANSPORT_ICE_UDP;
+}
+
+static gboolean
+get_credentials (WockyJingleTransportIface *iface,
+ gchar **ufrag, gchar **pwd)
+{
+ WockyJingleTransportIceUdp *transport =
+ WOCKY_JINGLE_TRANSPORT_ICEUDP (iface);
+ WockyJingleTransportIceUdpPrivate *priv = transport->priv;
+
+ if (!priv->ufrag || !priv->pwd)
+ return FALSE;
+
+ if (ufrag)
+ *ufrag = priv->ufrag;
+ if (pwd)
+ *pwd = priv->pwd;
+
+ return TRUE;
+}
+
+
+static void
+transport_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ WockyJingleTransportIfaceClass *klass = (WockyJingleTransportIfaceClass *) g_iface;
+
+ klass->parse_candidates = parse_candidates;
+
+ klass->new_local_candidates = new_local_candidates;
+ klass->inject_candidates = inject_candidates;
+ klass->send_candidates = send_candidates;
+
+ klass->get_remote_candidates = get_remote_candidates;
+ klass->get_local_candidates = get_local_candidates;
+ klass->get_transport_type = get_transport_type;
+ klass->get_credentials = get_credentials;
+}
+
+void
+jingle_transport_iceudp_register (WockyJingleFactory *factory)
+{
+ wocky_jingle_factory_register_transport (factory,
+ WOCKY_XMPP_NS_JINGLE_TRANSPORT_ICEUDP,
+ WOCKY_TYPE_JINGLE_TRANSPORT_ICEUDP);
+}
+
diff --git a/wocky/wocky-jingle-transport-iceudp.h b/wocky/wocky-jingle-transport-iceudp.h
new file mode 100644
index 0000000..ee9b6fa
--- /dev/null
+++ b/wocky/wocky-jingle-transport-iceudp.h
@@ -0,0 +1,69 @@
+/*
+ * wocky-jingle-transport-iceudp.h - Header for WockyJingleTransportIceUdp
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#if !defined (WOCKY_H_INSIDE) && !defined (WOCKY_COMPILATION)
+# error "Only <wocky/wocky.h> can be included directly."
+#endif
+
+#ifndef __JINGLE_TRANSPORT_ICEUDP_H__
+#define __JINGLE_TRANSPORT_ICEUDP_H__
+
+#include <glib-object.h>
+
+#include "wocky-jingle-types.h"
+
+G_BEGIN_DECLS
+
+typedef struct _WockyJingleTransportIceUdpClass WockyJingleTransportIceUdpClass;
+
+GType wocky_jingle_transport_iceudp_get_type (void);
+
+/* TYPE MACROS */
+#define WOCKY_TYPE_JINGLE_TRANSPORT_ICEUDP \
+ (wocky_jingle_transport_iceudp_get_type ())
+#define WOCKY_JINGLE_TRANSPORT_ICEUDP(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), WOCKY_TYPE_JINGLE_TRANSPORT_ICEUDP, \
+ WockyJingleTransportIceUdp))
+#define WOCKY_JINGLE_TRANSPORT_ICEUDP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), WOCKY_TYPE_JINGLE_TRANSPORT_ICEUDP, \
+ WockyJingleTransportIceUdpClass))
+#define WOCKY_IS_JINGLE_TRANSPORT_ICEUDP(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), WOCKY_TYPE_JINGLE_TRANSPORT_ICEUDP))
+#define WOCKY_IS_JINGLE_TRANSPORT_ICEUDP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), WOCKY_TYPE_JINGLE_TRANSPORT_ICEUDP))
+#define WOCKY_JINGLE_TRANSPORT_ICEUDP_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), WOCKY_TYPE_JINGLE_TRANSPORT_ICEUDP, \
+ WockyJingleTransportIceUdpClass))
+
+struct _WockyJingleTransportIceUdpClass {
+ GObjectClass parent_class;
+};
+
+typedef struct _WockyJingleTransportIceUdpPrivate WockyJingleTransportIceUdpPrivate;
+
+struct _WockyJingleTransportIceUdp {
+ GObject parent;
+ WockyJingleTransportIceUdpPrivate *priv;
+};
+
+void jingle_transport_iceudp_register (WockyJingleFactory *factory);
+
+G_END_DECLS
+
+#endif /* __JINGLE_TRANSPORT_ICEUDP_H__ */
+
diff --git a/wocky/wocky-jingle-transport-iface.c b/wocky/wocky-jingle-transport-iface.c
new file mode 100644
index 0000000..63a9339
--- /dev/null
+++ b/wocky/wocky-jingle-transport-iface.c
@@ -0,0 +1,282 @@
+/*
+ * wocky-jingle-transport-iface.c - Source for WockyJingleTransportIface
+ * Copyright (C) 2007-2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "wocky-jingle-transport-iface.h"
+
+#include <glib.h>
+
+#include "wocky-jingle-content.h"
+#include "wocky-jingle-session.h"
+
+WockyJingleTransportIface *
+wocky_jingle_transport_iface_new (GType type,
+ WockyJingleContent *content,
+ const gchar *transport_ns)
+{
+ g_return_val_if_fail (g_type_is_a (type, WOCKY_TYPE_JINGLE_TRANSPORT_IFACE),
+ NULL);
+
+ return g_object_new (type,
+ "content", content,
+ "transport-ns", transport_ns,
+ NULL);
+}
+
+void
+wocky_jingle_transport_iface_parse_candidates (WockyJingleTransportIface *self,
+ WockyNode *node, GError **error)
+{
+ void (*virtual_method)(WockyJingleTransportIface *,
+ WockyNode *, GError **) =
+ WOCKY_JINGLE_TRANSPORT_IFACE_GET_CLASS (self)->parse_candidates;
+
+ g_assert (virtual_method != NULL);
+ return virtual_method (self, node, error);
+}
+
+/* Takes in a list of slice-allocated WockyJingleCandidate structs */
+void
+wocky_jingle_transport_iface_new_local_candidates (WockyJingleTransportIface *self,
+ GList *candidates)
+{
+ void (*virtual_method)(WockyJingleTransportIface *,
+ GList *) =
+ WOCKY_JINGLE_TRANSPORT_IFACE_GET_CLASS (self)->new_local_candidates;
+
+ g_assert (virtual_method != NULL);
+ virtual_method (self, candidates);
+}
+
+/* Inserts candidates into the given <transport/> node, or equivalent, of a
+ * session-initiate, session-accept, content-add or content-accept action.
+ */
+void
+wocky_jingle_transport_iface_inject_candidates (
+ WockyJingleTransportIface *self,
+ WockyNode *transport_node)
+{
+ void (*virtual_method)(WockyJingleTransportIface *, WockyNode *) =
+ WOCKY_JINGLE_TRANSPORT_IFACE_GET_CLASS (self)->inject_candidates;
+
+ if (virtual_method != NULL)
+ virtual_method (self, transport_node);
+}
+
+/* Transmits outstanding or all candidates (if applicable and @all is set). */
+void
+wocky_jingle_transport_iface_send_candidates (
+ WockyJingleTransportIface *self,
+ gboolean all)
+{
+ void (*virtual_method) (WockyJingleTransportIface *, gboolean) =
+ WOCKY_JINGLE_TRANSPORT_IFACE_GET_CLASS (self)->send_candidates;
+
+ if (virtual_method != NULL)
+ virtual_method (self, all);
+}
+
+/* Returns TRUE if and only if @self has enough candidates to inject into a
+ * {session,content}-accept, and is connected.
+ */
+gboolean
+wocky_jingle_transport_iface_can_accept (WockyJingleTransportIface *self)
+{
+ WockyJingleTransportState state;
+ gboolean (*m) (WockyJingleTransportIface *) =
+ WOCKY_JINGLE_TRANSPORT_IFACE_GET_CLASS (self)->can_accept;
+
+ g_object_get (self, "state", &state, NULL);
+
+ if (state != WOCKY_JINGLE_TRANSPORT_STATE_CONNECTED)
+ return FALSE;
+
+ /* Only Raw UDP *needs* candidates in order to accept. */
+ if (m != NULL)
+ return m (self);
+ else
+ return TRUE;
+}
+
+GList *
+wocky_jingle_transport_iface_get_remote_candidates (
+ WockyJingleTransportIface *self)
+{
+ GList * (*virtual_method)(WockyJingleTransportIface *) =
+ WOCKY_JINGLE_TRANSPORT_IFACE_GET_CLASS (self)->get_remote_candidates;
+
+ g_assert (virtual_method != NULL);
+ return virtual_method (self);
+}
+
+GList *
+wocky_jingle_transport_iface_get_local_candidates (
+ WockyJingleTransportIface *self)
+{
+ GList * (*virtual_method)(WockyJingleTransportIface *) =
+ WOCKY_JINGLE_TRANSPORT_IFACE_GET_CLASS (self)->get_local_candidates;
+
+ g_assert (virtual_method != NULL);
+ return virtual_method (self);
+}
+
+gboolean
+jingle_transport_get_credentials (WockyJingleTransportIface *self,
+ gchar **ufrag, gchar **pwd)
+{
+ WockyJingleTransportIfaceClass *klass =
+ WOCKY_JINGLE_TRANSPORT_IFACE_GET_CLASS (self);
+
+ if (klass->get_credentials)
+ return klass->get_credentials (self, ufrag, pwd);
+ else
+ return FALSE;
+}
+
+WockyJingleTransportType
+wocky_jingle_transport_iface_get_transport_type (WockyJingleTransportIface *self)
+{
+ WockyJingleTransportType (*virtual_method)(void) =
+ WOCKY_JINGLE_TRANSPORT_IFACE_GET_CLASS (self)->get_transport_type;
+
+ g_assert (virtual_method != NULL);
+ return virtual_method ();
+}
+
+static void
+wocky_jingle_transport_iface_base_init (gpointer klass)
+{
+ static gboolean initialized = FALSE;
+
+ if (!initialized)
+ {
+ GParamSpec *param_spec;
+
+ param_spec = g_param_spec_object (
+ "content",
+ "WockyJingleContent object",
+ "Jingle content that's using this jingle transport object.",
+ WOCKY_TYPE_JINGLE_CONTENT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_string (
+ "transport-ns",
+ "Transport namespace",
+ "Namespace identifying the transport type.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_interface_install_property (klass, param_spec);
+
+ param_spec = g_param_spec_uint (
+ "state",
+ "Connection state for the transport.",
+ "Enum specifying the connection state of the transport.",
+ WOCKY_JINGLE_TRANSPORT_STATE_DISCONNECTED,
+ WOCKY_JINGLE_TRANSPORT_STATE_CONNECTED,
+ WOCKY_JINGLE_TRANSPORT_STATE_DISCONNECTED,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+
+ g_object_interface_install_property (klass, param_spec);
+
+ initialized = TRUE;
+ }
+}
+
+GType
+wocky_jingle_transport_iface_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo info = {
+ sizeof (WockyJingleTransportIfaceClass),
+ wocky_jingle_transport_iface_base_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL, /* class_init */
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL /* instance_init */
+ };
+
+ type = g_type_register_static (G_TYPE_INTERFACE, "WockyJingleTransportIface",
+ &info, 0);
+ }
+
+ return type;
+}
+
+WockyJingleCandidate *
+wocky_jingle_candidate_new (WockyJingleTransportProtocol protocol,
+ WockyJingleCandidateType type, const gchar *id, int component,
+ const gchar *address, int port, int generation, int preference,
+ const gchar *username, const gchar *password, int network)
+{
+ WockyJingleCandidate *c = g_slice_new0 (WockyJingleCandidate);
+
+ c->protocol = protocol;
+ c->type = type;
+ c->id = g_strdup (id);
+ c->address = g_strdup (address);
+ c->component = component;
+ c->port = port;
+ c->generation = generation;
+ c->preference = preference;
+ c->username = g_strdup (username);
+ c->password = g_strdup (password);
+ c->network = network;
+
+ return c;
+}
+
+void
+wocky_jingle_candidate_free (WockyJingleCandidate *c)
+{
+ g_free (c->id);
+ g_free (c->address);
+ g_free (c->username);
+ g_free (c->password);
+
+ g_slice_free (WockyJingleCandidate, c);
+}
+
+void
+jingle_transport_free_candidates (GList *candidates)
+{
+ while (candidates != NULL)
+ {
+ WockyJingleCandidate *c = (WockyJingleCandidate *) candidates->data;
+ wocky_jingle_candidate_free (c);
+ candidates = g_list_remove (candidates, c);
+ }
+}
+
diff --git a/wocky/wocky-jingle-transport-iface.h b/wocky/wocky-jingle-transport-iface.h
new file mode 100644
index 0000000..2b47d01
--- /dev/null
+++ b/wocky/wocky-jingle-transport-iface.h
@@ -0,0 +1,111 @@
+/*
+ * wocky-jingle-transport-iface.h - Header for WockyJingleTransportIface
+ * Copyright (C) 2007-2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#if !defined (WOCKY_H_INSIDE) && !defined (WOCKY_COMPILATION)
+# error "Only <wocky/wocky.h> can be included directly."
+#endif
+
+#ifndef __WOCKY_JINGLE_TRANSPORT_IFACE_H__
+#define __WOCKY_JINGLE_TRANSPORT_IFACE_H__
+
+#include <glib-object.h>
+
+#include "wocky-jingle-factory.h"
+#include "wocky-jingle-types.h"
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ WOCKY_JINGLE_TRANSPORT_STATE_DISCONNECTED,
+ WOCKY_JINGLE_TRANSPORT_STATE_CONNECTING,
+ WOCKY_JINGLE_TRANSPORT_STATE_CONNECTED
+} WockyJingleTransportState;
+
+typedef struct _WockyJingleTransportIface WockyJingleTransportIface;
+typedef struct _WockyJingleTransportIfaceClass WockyJingleTransportIfaceClass;
+
+struct _WockyJingleTransportIfaceClass {
+ GTypeInterface parent;
+
+ void (*parse_candidates) (WockyJingleTransportIface *,
+ WockyNode *, GError **);
+
+ void (*new_local_candidates) (WockyJingleTransportIface *, GList *);
+ void (*inject_candidates) (WockyJingleTransportIface *,
+ WockyNode *transport_node);
+ void (*send_candidates) (WockyJingleTransportIface *, gboolean all);
+ gboolean (*can_accept) (WockyJingleTransportIface *);
+
+ GList * (*get_remote_candidates) (WockyJingleTransportIface *);
+ GList * (*get_local_candidates) (WockyJingleTransportIface *);
+ gboolean (*get_credentials) (WockyJingleTransportIface *,
+ gchar **ufrag, gchar **pwd);
+
+ WockyJingleTransportType (*get_transport_type) (void);
+};
+
+GType wocky_jingle_transport_iface_get_type (void);
+
+/* TYPE MACROS */
+#define WOCKY_TYPE_JINGLE_TRANSPORT_IFACE \
+ (wocky_jingle_transport_iface_get_type ())
+#define WOCKY_JINGLE_TRANSPORT_IFACE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), WOCKY_TYPE_JINGLE_TRANSPORT_IFACE, WockyJingleTransportIface))
+#define WOCKY_IS_JINGLE_TRANSPORT_IFACE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), WOCKY_TYPE_JINGLE_TRANSPORT_IFACE))
+#define WOCKY_JINGLE_TRANSPORT_IFACE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE ((obj), WOCKY_TYPE_JINGLE_TRANSPORT_IFACE,\
+ WockyJingleTransportIfaceClass))
+
+void wocky_jingle_transport_iface_parse_candidates (WockyJingleTransportIface *,
+ WockyNode *, GError **);
+
+void wocky_jingle_transport_iface_new_local_candidates (
+ WockyJingleTransportIface *self,
+ GList *candidates);
+void wocky_jingle_transport_iface_inject_candidates (
+ WockyJingleTransportIface *self,
+ WockyNode *transport_node);
+void wocky_jingle_transport_iface_send_candidates (
+ WockyJingleTransportIface *self,
+ gboolean all);
+gboolean wocky_jingle_transport_iface_can_accept (
+ WockyJingleTransportIface *self);
+
+GList *wocky_jingle_transport_iface_get_remote_candidates (WockyJingleTransportIface *);
+GList *wocky_jingle_transport_iface_get_local_candidates (WockyJingleTransportIface *);
+WockyJingleTransportType wocky_jingle_transport_iface_get_transport_type (WockyJingleTransportIface *);
+gboolean jingle_transport_get_credentials (WockyJingleTransportIface *,
+ gchar **ufrag, gchar **pwd);
+
+WockyJingleTransportIface *wocky_jingle_transport_iface_new (
+ GType type, WockyJingleContent *content, const gchar *transport_ns);
+
+WockyJingleCandidate *wocky_jingle_candidate_new (WockyJingleTransportProtocol protocol,
+ WockyJingleCandidateType type, const gchar *id, int component,
+ const gchar *address, int port, int generation, int preference,
+ const gchar *username, const gchar *password, int network);
+
+void wocky_jingle_candidate_free (WockyJingleCandidate *c);
+void jingle_transport_free_candidates (GList *candidates);
+
+
+G_END_DECLS
+
+#endif /* #ifndef __WOCKY_JINGLE_TRANSPORT_IFACE_H__ */
diff --git a/wocky/wocky-jingle-transport-rawudp.c b/wocky/wocky-jingle-transport-rawudp.c
new file mode 100644
index 0000000..e9ecafa
--- /dev/null
+++ b/wocky/wocky-jingle-transport-rawudp.c
@@ -0,0 +1,403 @@
+/*
+ * wocky-jingle-transport-rawudp.c - Source for WockyJingleTransportRawUdp
+ *
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "wocky-jingle-transport-rawudp.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#define WOCKY_DEBUG_FLAG WOCKY_DEBUG_JINGLE
+
+#include "wocky-debug-internal.h"
+#include "wocky-jingle-content.h"
+#include "wocky-jingle-factory.h"
+#include "wocky-jingle-session.h"
+#include "wocky-namespaces.h"
+
+static void
+transport_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (WockyJingleTransportRawUdp,
+ wocky_jingle_transport_rawudp, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (WOCKY_TYPE_JINGLE_TRANSPORT_IFACE,
+ transport_iface_init));
+
+/* signal enum */
+enum
+{
+ NEW_CANDIDATES,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum
+{
+ PROP_CONTENT = 1,
+ PROP_TRANSPORT_NS,
+ PROP_STATE,
+ LAST_PROPERTY
+};
+
+struct _WockyJingleTransportRawUdpPrivate
+{
+ WockyJingleContent *content;
+ WockyJingleTransportState state;
+ gchar *transport_ns;
+
+ GList *local_candidates;
+ GList *remote_candidates;
+ gboolean dispose_has_run;
+};
+
+static void
+wocky_jingle_transport_rawudp_init (WockyJingleTransportRawUdp *obj)
+{
+ WockyJingleTransportRawUdpPrivate *priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (obj, WOCKY_TYPE_JINGLE_TRANSPORT_RAWUDP,
+ WockyJingleTransportRawUdpPrivate);
+ obj->priv = priv;
+
+ priv->dispose_has_run = FALSE;
+}
+
+static void
+wocky_jingle_transport_rawudp_dispose (GObject *object)
+{
+ WockyJingleTransportRawUdp *trans = WOCKY_JINGLE_TRANSPORT_RAWUDP (object);
+ WockyJingleTransportRawUdpPrivate *priv = trans->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ DEBUG ("dispose called");
+ priv->dispose_has_run = TRUE;
+
+ jingle_transport_free_candidates (priv->remote_candidates);
+ priv->remote_candidates = NULL;
+
+ jingle_transport_free_candidates (priv->local_candidates);
+ priv->local_candidates = NULL;
+
+ g_free (priv->transport_ns);
+ priv->transport_ns = NULL;
+
+ if (G_OBJECT_CLASS (wocky_jingle_transport_rawudp_parent_class)->dispose)
+ G_OBJECT_CLASS (wocky_jingle_transport_rawudp_parent_class)->dispose (object);
+}
+
+static void
+wocky_jingle_transport_rawudp_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleTransportRawUdp *trans = WOCKY_JINGLE_TRANSPORT_RAWUDP (object);
+ WockyJingleTransportRawUdpPrivate *priv = trans->priv;
+
+ switch (property_id) {
+ case PROP_CONTENT:
+ g_value_set_object (value, priv->content);
+ break;
+ case PROP_TRANSPORT_NS:
+ g_value_set_string (value, priv->transport_ns);
+ break;
+ case PROP_STATE:
+ g_value_set_uint (value, priv->state);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_jingle_transport_rawudp_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ WockyJingleTransportRawUdp *trans = WOCKY_JINGLE_TRANSPORT_RAWUDP (object);
+ WockyJingleTransportRawUdpPrivate *priv = trans->priv;
+
+ switch (property_id) {
+ case PROP_CONTENT:
+ priv->content = g_value_get_object (value);
+ break;
+ case PROP_TRANSPORT_NS:
+ g_free (priv->transport_ns);
+ priv->transport_ns = g_value_dup_string (value);
+ break;
+ case PROP_STATE:
+ priv->state = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_jingle_transport_rawudp_class_init (WockyJingleTransportRawUdpClass *cls)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (cls);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (cls, sizeof (WockyJingleTransportRawUdpPrivate));
+
+ object_class->get_property = wocky_jingle_transport_rawudp_get_property;
+ object_class->set_property = wocky_jingle_transport_rawudp_set_property;
+ object_class->dispose = wocky_jingle_transport_rawudp_dispose;
+
+ /* property definitions */
+ param_spec = g_param_spec_object ("content", "WockyJingleContent object",
+ "Jingle content object using this transport.",
+ WOCKY_TYPE_JINGLE_CONTENT,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONTENT, param_spec);
+
+ param_spec = g_param_spec_string ("transport-ns", "Transport namespace",
+ "Namespace identifying the transport type.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_TRANSPORT_NS, param_spec);
+
+ param_spec = g_param_spec_uint ("state",
+ "Connection state for the transport.",
+ "Enum specifying the connection state of the transport.",
+ WOCKY_JINGLE_TRANSPORT_STATE_DISCONNECTED,
+ WOCKY_JINGLE_TRANSPORT_STATE_CONNECTED,
+ WOCKY_JINGLE_TRANSPORT_STATE_DISCONNECTED,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_STATE, param_spec);
+
+ /* signal definitions */
+ signals[NEW_CANDIDATES] = g_signal_new (
+ "new-candidates",
+ G_TYPE_FROM_CLASS (cls),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+}
+
+static void
+parse_candidates (WockyJingleTransportIface *obj,
+ WockyNode *transport_node, GError **error)
+{
+ WockyJingleTransportRawUdp *t = WOCKY_JINGLE_TRANSPORT_RAWUDP (obj);
+ WockyJingleTransportRawUdpPrivate *priv = t->priv;
+ GList *candidates = NULL;
+ WockyNodeIter i;
+ WockyNode *node;
+
+ DEBUG ("called");
+
+ if (priv->remote_candidates != NULL)
+ {
+ DEBUG ("already have raw udp candidates, ignoring extra ones");
+ return;
+ }
+
+ wocky_node_iter_init (&i, transport_node, "candidate", NULL);
+ while (wocky_node_iter_next (&i, &node))
+ {
+ const gchar *id, *ip, *str;
+ guint port, gen, component = 1;
+ WockyJingleCandidate *c;
+
+ str = wocky_node_get_attribute (node, "component");
+ if (str != NULL)
+ component = atoi (str);
+
+ if ((component != 1) && (component != 2))
+ {
+ DEBUG ("Ignoring non-RTP/RTCP component %d", component);
+ continue;
+ }
+
+ id = wocky_node_get_attribute (node, "id");
+ if (id == NULL)
+ break;
+
+ ip = wocky_node_get_attribute (node, "ip");
+ if (ip == NULL)
+ break;
+
+ str = wocky_node_get_attribute (node, "port");
+ if (str == NULL)
+ break;
+ port = atoi (str);
+
+ str = wocky_node_get_attribute (node, "generation");
+ if (str == NULL)
+ break;
+ gen = atoi (str);
+
+ c = wocky_jingle_candidate_new (WOCKY_JINGLE_TRANSPORT_PROTOCOL_UDP,
+ WOCKY_JINGLE_CANDIDATE_TYPE_LOCAL, id, component, ip, port,
+ gen, 1.0, NULL, NULL, 0);
+
+ candidates = g_list_append (candidates, c);
+ }
+
+ if (wocky_node_iter_next (&i, NULL))
+ {
+ DEBUG ("not all nodes were processed, reporting error");
+ /* rollback these */
+ jingle_transport_free_candidates (candidates);
+ g_set_error (error, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST,
+ "invalid candidate");
+ return;
+ }
+
+ DEBUG ("emitting %d new remote candidates", g_list_length (candidates));
+ g_signal_emit (obj, signals[NEW_CANDIDATES], 0, candidates);
+ priv->remote_candidates = candidates;
+}
+
+static void
+inject_candidates (WockyJingleTransportIface *obj,
+ WockyNode *transport_node)
+{
+ WockyJingleTransportRawUdp *self = WOCKY_JINGLE_TRANSPORT_RAWUDP (obj);
+ WockyJingleTransportRawUdpPrivate *priv = self->priv;
+ WockyJingleCandidate *c;
+ GList *li;
+ gchar port_str[16], comp_str[16];
+ WockyNode *cnode;
+
+ /* If we don't have the local candidates yet, we should've waited with
+ * the session initiation, or can_accept would have returned FALSE.
+ */
+ g_assert (priv->local_candidates != NULL);
+
+ for (li = priv->local_candidates; li != NULL; li = li->next)
+ {
+ c = (WockyJingleCandidate *) li->data;
+ sprintf (port_str, "%d", c->port);
+ sprintf (comp_str, "%d", c->component);
+
+ cnode = wocky_node_add_child (transport_node, "candidate");
+ wocky_node_set_attributes (cnode,
+ "ip", c->address,
+ "port", port_str,
+ "generation", "0",
+ "id", c->id,
+ "component", comp_str,
+ NULL);
+ }
+}
+
+/* Takes in a list of slice-allocated WockyJingleCandidate structs */
+static void
+new_local_candidates (WockyJingleTransportIface *obj, GList *new_candidates)
+{
+ WockyJingleTransportRawUdp *transport =
+ WOCKY_JINGLE_TRANSPORT_RAWUDP (obj);
+ WockyJingleTransportRawUdpPrivate *priv = transport->priv;
+
+ if (priv->local_candidates != NULL)
+ {
+ DEBUG ("ignoring new local candidates for RAW UDP");
+ jingle_transport_free_candidates (new_candidates);
+ return;
+ }
+
+ priv->local_candidates = new_candidates;
+}
+
+static gboolean
+can_accept (WockyJingleTransportIface *iface)
+{
+ WockyJingleTransportRawUdp *self = WOCKY_JINGLE_TRANSPORT_RAWUDP (iface);
+
+ return (self->priv->local_candidates != NULL);
+}
+
+static GList *
+get_local_candidates (WockyJingleTransportIface *iface)
+{
+ WockyJingleTransportRawUdp *transport =
+ WOCKY_JINGLE_TRANSPORT_RAWUDP (iface);
+ WockyJingleTransportRawUdpPrivate *priv = transport->priv;
+
+ return priv->local_candidates;
+}
+
+static GList *
+get_remote_candidates (WockyJingleTransportIface *iface)
+{
+ WockyJingleTransportRawUdp *transport =
+ WOCKY_JINGLE_TRANSPORT_RAWUDP (iface);
+ WockyJingleTransportRawUdpPrivate *priv = transport->priv;
+
+ return priv->remote_candidates;
+}
+
+static WockyJingleTransportType
+get_transport_type (void)
+{
+ DEBUG ("called");
+
+ return JINGLE_TRANSPORT_RAW_UDP;
+}
+
+static void
+transport_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ WockyJingleTransportIfaceClass *klass = (WockyJingleTransportIfaceClass *) g_iface;
+
+ klass->parse_candidates = parse_candidates;
+
+ klass->new_local_candidates = new_local_candidates;
+ klass->inject_candidates = inject_candidates;
+ /* Not implementing _send: XEP-0177 says that the candidates live in
+ * content-{add,accept}, not in transport-info.
+ */
+ klass->can_accept = can_accept;
+
+ klass->get_remote_candidates = get_remote_candidates;
+ klass->get_local_candidates = get_local_candidates;
+ klass->get_transport_type = get_transport_type;
+}
+
+void
+jingle_transport_rawudp_register (WockyJingleFactory *factory)
+{
+ wocky_jingle_factory_register_transport (factory,
+ WOCKY_XMPP_NS_JINGLE_TRANSPORT_RAWUDP,
+ WOCKY_TYPE_JINGLE_TRANSPORT_RAWUDP);
+}
+
diff --git a/wocky/wocky-jingle-transport-rawudp.h b/wocky/wocky-jingle-transport-rawudp.h
new file mode 100644
index 0000000..aa49d33
--- /dev/null
+++ b/wocky/wocky-jingle-transport-rawudp.h
@@ -0,0 +1,69 @@
+/*
+ * wocky-jingle-transport-rawudp.h - Header for WockyJingleTransportRawUdp
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#if !defined (WOCKY_H_INSIDE) && !defined (WOCKY_COMPILATION)
+# error "Only <wocky/wocky.h> can be included directly."
+#endif
+
+#ifndef __JINGLE_TRANSPORT_RAWUDP_H__
+#define __JINGLE_TRANSPORT_RAWUDP_H__
+
+#include <glib-object.h>
+
+#include "wocky-jingle-types.h"
+
+G_BEGIN_DECLS
+
+typedef struct _WockyJingleTransportRawUdpClass WockyJingleTransportRawUdpClass;
+
+GType wocky_jingle_transport_rawudp_get_type (void);
+
+/* TYPE MACROS */
+#define WOCKY_TYPE_JINGLE_TRANSPORT_RAWUDP \
+ (wocky_jingle_transport_rawudp_get_type ())
+#define WOCKY_JINGLE_TRANSPORT_RAWUDP(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), WOCKY_TYPE_JINGLE_TRANSPORT_RAWUDP, \
+ WockyJingleTransportRawUdp))
+#define WOCKY_JINGLE_TRANSPORT_RAWUDP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), WOCKY_TYPE_JINGLE_TRANSPORT_RAWUDP, \
+ WockyJingleTransportRawUdpClass))
+#define WOCKY_IS_JINGLE_TRANSPORT_RAWUDP(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), WOCKY_TYPE_JINGLE_TRANSPORT_RAWUDP))
+#define WOCKY_IS_JINGLE_TRANSPORT_RAWUDP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), WOCKY_TYPE_JINGLE_TRANSPORT_RAWUDP))
+#define WOCKY_JINGLE_TRANSPORT_RAWUDP_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), WOCKY_TYPE_JINGLE_TRANSPORT_RAWUDP, \
+ WockyJingleTransportRawUdpClass))
+
+struct _WockyJingleTransportRawUdpClass {
+ GObjectClass parent_class;
+};
+
+typedef struct _WockyJingleTransportRawUdpPrivate WockyJingleTransportRawUdpPrivate;
+
+struct _WockyJingleTransportRawUdp {
+ GObject parent;
+ WockyJingleTransportRawUdpPrivate *priv;
+};
+
+void jingle_transport_rawudp_register (WockyJingleFactory *factory);
+
+G_END_DECLS
+
+#endif /* __JINGLE_TRANSPORT_RAWUDP_H__ */
+
diff --git a/wocky/wocky-jingle-types.h b/wocky/wocky-jingle-types.h
new file mode 100644
index 0000000..9378371
--- /dev/null
+++ b/wocky/wocky-jingle-types.h
@@ -0,0 +1,157 @@
+/*
+ * wocky-jingle-types.h - Header for Jingle-related enums and typedefs
+ * Copyright © 2008–2012 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#if !defined (WOCKY_H_INSIDE) && !defined (WOCKY_COMPILATION)
+# error "Only <wocky/wocky.h> can be included directly."
+#endif
+
+#ifndef __WOCKY_JINGLE_TYPES_H__
+#define __WOCKY_JINGLE_TYPES_H__
+
+typedef struct _WockyJingleFactory WockyJingleFactory;
+typedef struct _WockyJingleSession WockyJingleSession;
+typedef struct _WockyJingleContent WockyJingleContent;
+typedef struct _WockyJingleTransportGoogle WockyJingleTransportGoogle;
+typedef struct _WockyJingleTransportRawUdp WockyJingleTransportRawUdp;
+typedef struct _WockyJingleTransportIceUdp WockyJingleTransportIceUdp;
+typedef struct _WockyJingleMediaRtp WockyJingleMediaRtp;
+typedef struct _WockyJingleCandidate WockyJingleCandidate;
+
+typedef enum { /*< skip >*/
+ /* not a jingle message */
+ WOCKY_JINGLE_DIALECT_ERROR,
+ /* old libjingle3 gtalk variant */
+ WOCKY_JINGLE_DIALECT_GTALK3,
+ /* new gtalk variant */
+ WOCKY_JINGLE_DIALECT_GTALK4,
+ /* jingle in the old 0.15 version days */
+ WOCKY_JINGLE_DIALECT_V015,
+ /* current jingle standard */
+ WOCKY_JINGLE_DIALECT_V032
+} WockyJingleDialect;
+
+#define WOCKY_JINGLE_DIALECT_IS_GOOGLE(d)\
+ ((d == WOCKY_JINGLE_DIALECT_GTALK3) || (d == WOCKY_JINGLE_DIALECT_GTALK4))
+
+/**
+ * WockyJingleState:
+ * @WOCKY_JINGLE_STATE_PENDING_CREATED: on outgoing sessions, no offer has been
+ * sent to the peer yet.
+ * @WOCKY_JINGLE_STATE_PENDING_INITIATE_SENT: on outgoing sessions, we have sent
+ * the session-initiate and are awaiting the peer's acknowledgement.
+ * @WOCKY_JINGLE_STATE_PENDING_INITIATED: on outgoing sessions, the peer has
+ * received our session-initiate and we're waiting for them to accept; on
+ * incoming sessions, the peer is waiting for us to accept.
+ * @WOCKY_JINGLE_STATE_PENDING_ACCEPT_SENT: on incoming sessions, we have sent
+ * session-accept and are waiting for the peer to acknowledge it.
+ * @WOCKY_JINGLE_STATE_ACTIVE: the session is active.
+ * @WOCKY_JINGLE_STATE_ENDED: the session has ended. The
+ * #WockyJingleSession::terminated signal describes how the session ended.
+ *
+ * Possible states of a #WockyJingleSession.
+ */
+typedef enum { /*< skip >*/
+ /*< private >*/
+ WOCKY_JINGLE_STATE_INVALID = -1,
+ /*< public >*/
+ WOCKY_JINGLE_STATE_PENDING_CREATED = 0,
+ WOCKY_JINGLE_STATE_PENDING_INITIATE_SENT,
+ WOCKY_JINGLE_STATE_PENDING_INITIATED,
+ WOCKY_JINGLE_STATE_PENDING_ACCEPT_SENT,
+ WOCKY_JINGLE_STATE_ACTIVE,
+ WOCKY_JINGLE_STATE_ENDED,
+ /*< private >*/
+ WOCKY_N_JINGLE_STATES
+} WockyJingleState;
+
+typedef enum { /*< skip >*/
+ WOCKY_JINGLE_ACTION_UNKNOWN,
+ WOCKY_JINGLE_ACTION_CONTENT_ACCEPT,
+ WOCKY_JINGLE_ACTION_CONTENT_ADD,
+ WOCKY_JINGLE_ACTION_CONTENT_MODIFY,
+ WOCKY_JINGLE_ACTION_CONTENT_REMOVE,
+ WOCKY_JINGLE_ACTION_CONTENT_REPLACE,
+ WOCKY_JINGLE_ACTION_CONTENT_REJECT,
+ WOCKY_JINGLE_ACTION_SESSION_ACCEPT,
+ WOCKY_JINGLE_ACTION_SESSION_INFO,
+ WOCKY_JINGLE_ACTION_SESSION_INITIATE,
+ WOCKY_JINGLE_ACTION_SESSION_TERMINATE,
+ WOCKY_JINGLE_ACTION_TRANSPORT_INFO,
+ WOCKY_JINGLE_ACTION_TRANSPORT_ACCEPT,
+ WOCKY_JINGLE_ACTION_DESCRIPTION_INFO,
+ WOCKY_JINGLE_ACTION_INFO
+} WockyJingleAction;
+
+typedef enum { /*< skip >*/
+ WOCKY_JINGLE_CONTENT_SENDERS_NONE,
+ WOCKY_JINGLE_CONTENT_SENDERS_INITIATOR,
+ WOCKY_JINGLE_CONTENT_SENDERS_RESPONDER,
+ WOCKY_JINGLE_CONTENT_SENDERS_BOTH
+} WockyJingleContentSenders;
+
+typedef enum { /*< skip >*/
+ JINGLE_TRANSPORT_UNKNOWN,
+ JINGLE_TRANSPORT_GOOGLE_P2P,
+ JINGLE_TRANSPORT_RAW_UDP,
+ JINGLE_TRANSPORT_ICE_UDP,
+} WockyJingleTransportType;
+
+typedef enum { /*< skip >*/
+ WOCKY_JINGLE_TRANSPORT_PROTOCOL_UDP,
+ WOCKY_JINGLE_TRANSPORT_PROTOCOL_TCP
+} WockyJingleTransportProtocol;
+
+typedef enum { /*< skip >*/
+ WOCKY_JINGLE_CANDIDATE_TYPE_LOCAL,
+ WOCKY_JINGLE_CANDIDATE_TYPE_STUN,
+ WOCKY_JINGLE_CANDIDATE_TYPE_RELAY
+} WockyJingleCandidateType;
+
+/**
+ * WockyJingleReason:
+ * @WOCKY_JINGLE_REASON_UNKNOWN: no known reason
+ *
+ * The reason for a Jingle action occurring—specifically, the reason for
+ * terminating a call. See <ulink
+ * url="http://xmpp.org/extensions/xep-0166.html#def-reason">XEP-0166 Jingle
+ * §7.4</ulink> for definitions of the codes.
+ */
+typedef enum
+{
+ WOCKY_JINGLE_REASON_UNKNOWN,
+ WOCKY_JINGLE_REASON_ALTERNATIVE_SESSION,
+ WOCKY_JINGLE_REASON_BUSY,
+ WOCKY_JINGLE_REASON_CANCEL,
+ WOCKY_JINGLE_REASON_CONNECTIVITY_ERROR,
+ WOCKY_JINGLE_REASON_DECLINE,
+ WOCKY_JINGLE_REASON_EXPIRED,
+ WOCKY_JINGLE_REASON_FAILED_APPLICATION,
+ WOCKY_JINGLE_REASON_FAILED_TRANSPORT,
+ WOCKY_JINGLE_REASON_GENERAL_ERROR,
+ WOCKY_JINGLE_REASON_GONE,
+ WOCKY_JINGLE_REASON_INCOMPATIBLE_PARAMETERS,
+ WOCKY_JINGLE_REASON_MEDIA_ERROR,
+ WOCKY_JINGLE_REASON_SECURITY_ERROR,
+ WOCKY_JINGLE_REASON_SUCCESS,
+ WOCKY_JINGLE_REASON_TIMEOUT,
+ WOCKY_JINGLE_REASON_UNSUPPORTED_APPLICATIONS,
+ WOCKY_JINGLE_REASON_UNSUPPORTED_TRANSPORTS
+} WockyJingleReason;
+
+
+#endif /* __WOCKY_JINGLE_TYPES_H__ */
diff --git a/wocky/wocky-meta-porter.c b/wocky/wocky-meta-porter.c
index b6bb760..f1ca936 100644
--- a/wocky/wocky-meta-porter.c
+++ b/wocky/wocky-meta-porter.c
@@ -1222,25 +1222,13 @@ register_porter_handler (StanzaHandler *handler,
g_assert (g_hash_table_lookup (handler->porters, porter) == NULL);
- if (handler->contact != NULL)
- {
- gchar *jid = wocky_contact_dup_jid (handler->contact);
-
- id = wocky_porter_register_handler_from_by_stanza (porter,
- handler->type, handler->sub_type, jid,
- handler->priority, porter_handler_cb, handler,
- handler->stanza);
-
- g_free (jid);
- }
- else
- {
- id = wocky_porter_register_handler_from_anyone_by_stanza (porter,
- handler->type, handler->sub_type,
- handler->priority, porter_handler_cb, handler,
- handler->stanza);
- }
-
+ /* If handler->contact is not NULL, we know that this c2s porter is a
+ * connection to them, so we still don't need to tell it to match the sender.
+ */
+ id = wocky_porter_register_handler_from_anyone_by_stanza (porter,
+ handler->type, handler->sub_type,
+ handler->priority, porter_handler_cb, handler,
+ handler->stanza);
g_hash_table_insert (handler->porters, porter, GUINT_TO_POINTER (id));
g_object_weak_ref (G_OBJECT (porter),
diff --git a/wocky/wocky-muc.c b/wocky/wocky-muc.c
index 2799a90..bf52239 100644
--- a/wocky/wocky-muc.c
+++ b/wocky/wocky-muc.c
@@ -18,6 +18,18 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
+/**
+ * SECTION: wocky-muc
+ * @title: WockyMuc
+ * @short_description: multi-user chat rooms
+ * @include: wocky/wocky.h
+ *
+ * Represents a multi-user chat room. Because the MUC protocol is so terrible,
+ * you will find yourself consulting <ulink
+ * url='http://xmpp.org/extensions/xep-0045.html'>XEP-0045</ulink> and shedding
+ * more than a few tears while using this class.
+ */
+
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
@@ -367,26 +379,74 @@ wocky_muc_class_init (WockyMucClass *klass)
G_TYPE_NONE, 2,
WOCKY_TYPE_STANZA, G_TYPE_UINT);
+ /**
+ * WockyMuc::joined:
+ * @muc: the MUC
+ * @stanza: the presence stanza
+ * @codes: bitwise OR of %WockyMucStatusCode flags with miscellaneous
+ * information about the MUC
+ *
+ * Emitted when the local user successfully joins @muc.
+ */
signals[SIG_JOINED] = g_signal_new ("joined", ctype,
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
_wocky_signals_marshal_VOID__POINTER_UINT,
G_TYPE_NONE, 2,
- WOCKY_TYPE_STANZA, G_TYPE_UINT);
-
+ WOCKY_TYPE_STANZA,
+ G_TYPE_UINT);
+
+ /**
+ * WockyMuc::error:
+ * @muc: the MUC
+ * @stanza: the presence stanza
+ * @error_type: the type of error
+ * @error: an error in domain #WOCKY_XMPP_ERROR, whose message (if not %NULL)
+ * is a human-readable message from the server
+ *
+ * Emitted when a presence error is received from the MUC, which is generally
+ * in response to trying to join the MUC.
+ */
signals[SIG_PRESENCE_ERROR] = g_signal_new ("error", ctype,
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
- _wocky_signals_marshal_VOID__OBJECT_ENUM_STRING,
+ _wocky_signals_marshal_VOID__OBJECT_ENUM_BOXED,
G_TYPE_NONE, 3,
- WOCKY_TYPE_STANZA, WOCKY_TYPE_XMPP_ERROR, G_TYPE_STRING);
-
- /* These signals convey actor(jid) + reason */
+ WOCKY_TYPE_STANZA,
+ WOCKY_TYPE_XMPP_ERROR_TYPE,
+ G_TYPE_ERROR);
+
+ /**
+ * WockyMuc::permissions:
+ * @muc: the muc
+ * @stanza: the presence stanza heralding the change
+ * @codes: bitwise OR of %WockyMucStatusCode flags
+ * @actor_jid: the JID of the user who changed our permissions, or %NULL
+ * @reason: a human-readable reason for the change, or %NULL
+ *
+ * Emitted when our permissions within the MUC are changed.
+ */
signals[SIG_PERM_CHANGE] = g_signal_new ("permissions", ctype,
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
_wocky_signals_marshal_VOID__POINTER_UINT_POINTER_POINTER,
G_TYPE_NONE, 4,
- WOCKY_TYPE_STANZA, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING);
-
- /* and these two pass on any message as well: */
+ WOCKY_TYPE_STANZA,
+ G_TYPE_UINT,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ /**
+ * WockyMuc::parted:
+ * @muc: the MUC
+ * @stanza: the presence stanza
+ * @codes: bitwise OR of %WockyMucStatusCode flags describing why the user
+ * left the MUC
+ * @actor: if the user was removed from the MUC by another participant, that
+ * participant's JID
+ * @reason: if the user was removed from the MUC by another participant, a
+ * human-readable reason given by that participant
+ * @message: a parting message we provided to other participants, or %NULL
+ *
+ * Emitted when the local user leaves the MUC, whether by choice or by force.
+ */
signals[SIG_PARTED] = g_signal_new ("parted", ctype,
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
_wocky_signals_marshal_VOID__OBJECT_UINT_STRING_STRING_STRING,
@@ -397,42 +457,101 @@ wocky_muc_class_init (WockyMucClass *klass)
G_TYPE_STRING, /* reason */
G_TYPE_STRING); /* message: usually none, but allowed by spec */
+ /**
+ * WockyMuc::left:
+ * @muc: the MUC
+ * @stanza: the presence stanza
+ * @codes: bitwise OR of %WockyMucStatusCode flags describing why @member
+ * left the MUC
+ * @member: the (now ex-)member of the MUC who left
+ * @actor: if @member was removed from the MUC by another participant, that
+ * participant's JID
+ * @reason: if @member was removed from the MUC by another participant, a
+ * human-readable reason given by that participant
+ * @message: a parting message provided by @member, or %NULL
+ *
+ * Emitted when another participant leaves, or is kicked from, the MUC
+ */
signals[SIG_LEFT] = g_signal_new ("left", ctype,
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
_wocky_signals_marshal_VOID__OBJECT_UINT_POINTER_STRING_STRING_STRING,
G_TYPE_NONE, 6,
WOCKY_TYPE_STANZA,
G_TYPE_UINT,
- G_TYPE_POINTER, /* member struct */
- G_TYPE_STRING, /* actor jid */
- G_TYPE_STRING, /* reason */
- G_TYPE_STRING); /* message, if any */
-
+ G_TYPE_POINTER,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ /**
+ * WockyMuc::message:
+ * @muc: the MUC
+ * @stanza: the incoming message stanza
+ * @message_type: the message's type
+ * @id: the stanza's identifier (which may be %NULL if neither the sender nor
+ * the MUC specified one)
+ * @timestamp: for messages received as scrollback when joining the MUC, the
+ * time the message was sent; %NULL for messages received while in the MUC
+ * @sender: a %WockyMucMember struct describing the sender of the message
+ * @body: the body of the message, or %NULL
+ * @subject: the new subject for the MUC, or %NULL
+ * @state: whether @sender is currently typing.
+ *
+ * Emitted when a non-error message stanza is received. This may indicate:
+ *
+ * <itemizedlist>
+ * <listitem>if @body is not %NULL, a message sent by @sender to the
+ * MUC;</listitem>
+ * <listitem>or, if @subject is not %NULL, @sender changed the subject of the
+ * MUC;</listitem>
+ * <listitem>additionally, that @sender is typing, or maybe stopped typing,
+ * depending on @state.</listitem>
+ * </itemizedlist>
+ */
signals[SIG_MSG] = g_signal_new ("message", ctype,
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
_wocky_signals_marshal_VOID__OBJECT_ENUM_STRING_LONG_POINTER_STRING_STRING_ENUM,
G_TYPE_NONE, 8,
WOCKY_TYPE_STANZA,
- WOCKY_TYPE_MUC_MSG_TYPE, /* WockyMucMsgType */
- G_TYPE_STRING, /* XMPP msg ID */
- G_TYPE_DATE_TIME, /* timestamp */
- G_TYPE_POINTER, /* WockyMucMember * */
- G_TYPE_STRING, /* content */
- G_TYPE_STRING, /* subject */
- WOCKY_TYPE_MUC_MSG_STATE); /* WockyMucMsgState */
-
+ WOCKY_TYPE_MUC_MSG_TYPE,
+ G_TYPE_STRING,
+ G_TYPE_DATE_TIME,
+ G_TYPE_POINTER,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ WOCKY_TYPE_MUC_MSG_STATE);
+
+ /**
+ * WockyMuc::message-error:
+ * @muc: the MUC
+ * @stanza: the incoming %WOCKY_STANZA_SUB_TYPE_ERROR message
+ * @message_type: the type of the message which was rejected
+ * @id: the identifier for the original message and this error (which may be
+ * %NULL)
+ * @timestamp: the timestamp attached to the original message, which is
+ * probably %NULL because timestamps are only attached to scrollback messages
+ * @member: a %WockyMucMember struct describing the sender of the original
+ * message (which is, we presume, us)
+ * @body: the body of the message which failed to send
+ * @error_type: the type of error
+ * @error: an error in domain %WOCKY_XMPP_ERROR, whose message (if not %NULL)
+ * is a human-readable message from the server
+ *
+ * Emitted when we receive an error from the MUC in response to sending a
+ * message stanza to the MUC.
+ */
signals[SIG_MSG_ERR] = g_signal_new ("message-error", ctype,
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
- _wocky_signals_marshal_VOID__OBJECT_ENUM_STRING_LONG_POINTER_STRING_ENUM_ENUM,
+ _wocky_signals_marshal_VOID__OBJECT_ENUM_STRING_LONG_POINTER_STRING_ENUM_BOXED,
G_TYPE_NONE, 8,
WOCKY_TYPE_STANZA,
- WOCKY_TYPE_MUC_MSG_TYPE, /* WockyMucMsgType */
- G_TYPE_STRING, /* XMPP msg ID */
- G_TYPE_DATE_TIME, /* timestamp */
- G_TYPE_POINTER, /* WockyMucMember * */
- G_TYPE_STRING, /* content */
- WOCKY_TYPE_XMPP_ERROR, /* WockyXmppError */
- WOCKY_TYPE_XMPP_ERROR_TYPE); /* error type */
+ WOCKY_TYPE_MUC_MSG_TYPE,
+ G_TYPE_STRING,
+ G_TYPE_DATE_TIME,
+ G_TYPE_POINTER,
+ G_TYPE_STRING,
+ WOCKY_TYPE_XMPP_ERROR_TYPE,
+ G_TYPE_ERROR);
signals[SIG_FILL_PRESENCE] = g_signal_new ("fill-presence", ctype,
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
@@ -1195,9 +1314,10 @@ handle_presence_error (WockyMuc *muc,
{
gboolean ok = FALSE;
WockyMucPrivate *priv = muc->priv;
+ WockyXmppErrorType type;
GError *error = NULL;
- wocky_stanza_extract_errors (stanza, NULL, &error, NULL, NULL);
+ wocky_stanza_extract_errors (stanza, &type, &error, NULL, NULL);
if (priv->state >= WOCKY_MUC_JOINED)
{
@@ -1207,8 +1327,7 @@ handle_presence_error (WockyMuc *muc,
error->message);
}
- g_signal_emit (muc, signals[SIG_PRESENCE_ERROR], 0, stanza, error->code,
- error->message);
+ g_signal_emit (muc, signals[SIG_PRESENCE_ERROR], 0, stanza, type, error);
g_clear_error (&error);
return ok;
@@ -1428,7 +1547,7 @@ handle_message (WockyPorter *porter,
wocky_stanza_extract_errors (stanza, &etype, &error, NULL, NULL);
g_signal_emit (muc, signals[SIG_MSG_ERR], 0,
- stanza, mtype, id, datetime, who, body, error->code, etype);
+ stanza, mtype, id, datetime, who, body, etype, error);
g_clear_error (&error);
}
else
diff --git a/wocky/wocky-muc.h b/wocky/wocky-muc.h
index c5b348d..2deb4c0 100644
--- a/wocky/wocky-muc.h
+++ b/wocky/wocky-muc.h
@@ -26,7 +26,7 @@
#include <glib-object.h>
-#include "wocky-muc-enumtypes.h"
+#include "wocky-enumtypes.h"
#include "wocky-namespaces.h"
#include "wocky-porter.h"
@@ -230,8 +230,8 @@ typedef enum {
/**
* WockyMucMember:
- * @from: the JID of the member (room@server/nick)
- * @jid: the JID of the owner (owner@domain/resource)
+ * @from: the JID of the member (room&commat;server/nick)
+ * @jid: the JID of the owner (owner&commat;domain/resource)
* @nick: the nickname of the member
* @role: the #WockyMucRole of the member
* @affiliation: the #WockyMucAffiliation of the member
@@ -251,17 +251,18 @@ typedef struct {
GType wocky_muc_get_type (void);
-struct _WockyMucClass {
- /*<private>*/
- GObjectClass parent_class;
-};
-
struct _WockyMuc {
/*<private>*/
GObject parent;
WockyMucPrivate *priv;
};
+struct _WockyMucClass {
+ /*<private>*/
+ GObjectClass parent_class;
+};
+
+
/* TYPE MACROS */
#define WOCKY_TYPE_MUC (wocky_muc_get_type ())
@@ -294,16 +295,6 @@ WockyStanza *wocky_muc_create_presence (WockyMuc *muc,
WockyStanzaSubType type,
const gchar *status);
-/* initiate */
-void wocky_muc_initiate_async (WockyMuc *muc,
- GAsyncReadyCallback callback,
- GCancellable *cancel,
- gpointer data);
-
-gboolean wocky_muc_initiate_finish (GObject *source,
- GAsyncResult *res,
- GError **error);
-
/* join */
void wocky_muc_join (WockyMuc *muc,
GCancellable *cancel);
diff --git a/wocky/wocky-namespaces.h b/wocky/wocky-namespaces.h
index 5ea47c6..9896c4f 100644
--- a/wocky/wocky-namespaces.h
+++ b/wocky/wocky-namespaces.h
@@ -84,6 +84,20 @@
#define WOCKY_XEP77_NS_REGISTER \
"jabber:iq:register"
+/* Namespaces for XEP-0166 draft v0.15, the most capable Jingle dialect
+ * supported by telepathy-gabble < 0.7.16, including the versions shipped with
+ * Maemo Chinook and Diablo.
+ */
+#define WOCKY_XMPP_NS_JINGLE015 \
+ "http://jabber.org/protocol/jingle"
+
+/* RTP audio capability in Jingle v0.15 (obsoleted by WOCKY_XMPP_NS_JINGLE_RTP) */
+#define WOCKY_XMPP_NS_JINGLE_DESCRIPTION_AUDIO \
+ "http://jabber.org/protocol/jingle/description/audio"
+/* RTP video capability in Jingle v0.15 (obsoleted by WOCKY_XMPP_NS_JINGLE_RTP) */
+#define WOCKY_XMPP_NS_JINGLE_DESCRIPTION_VIDEO \
+ "http://jabber.org/protocol/jingle/description/video"
+
/* XEP-0166 Jingle */
#define WOCKY_XMPP_NS_JINGLE \
"urn:xmpp:jingle:1"
@@ -102,6 +116,42 @@
#define WOCKY_XMPP_NS_JINGLE_RTP_VIDEO \
"urn:xmpp:jingle:apps:rtp:video"
+/* ProtoXEPs for rtcp-fb and rtp-hdrext */
+#define WOCKY_XMPP_NS_JINGLE_RTCP_FB "urn:xmpp:jingle:apps:rtp:rtcp-fb:0"
+#define WOCKY_XMPP_NS_JINGLE_RTP_HDREXT "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"
+
+/* Google's Jingle dialect */
+#define WOCKY_XMPP_NS_GOOGLE_SESSION "http://www.google.com/session"
+/* Audio capability in Google Jingle dialect */
+#define WOCKY_XMPP_NS_GOOGLE_SESSION_PHONE "http://www.google.com/session/phone"
+/* Video capability in Google's Jingle dialect */
+#define WOCKY_XMPP_NS_GOOGLE_SESSION_VIDEO "http://www.google.com/session/video"
+/* File transfer capability in Google's Jingle dialect */
+#define WOCKY_XMPP_NS_GOOGLE_SESSION_SHARE "http://www.google.com/session/share"
+
+/* google-p2p transport */
+#define WOCKY_XMPP_NS_GOOGLE_TRANSPORT_P2P "http://www.google.com/transport/p2p"
+/* Jingle RAW-UDP transport */
+#define WOCKY_XMPP_NS_JINGLE_TRANSPORT_RAWUDP "urn:xmpp:jingle:transports:raw-udp:1"
+/* Jingle ICE-UDP transport */
+#define WOCKY_XMPP_NS_JINGLE_TRANSPORT_ICEUDP "urn:xmpp:jingle:transports:ice-udp:1"
+
+/* Here are some "quirk" pseudo-namespaces imported from Gabble with the Jingle
+ * code and used to work around known bugs in particular Jingle
+ * implementations. ASCII BEL is illegal in XML, so these cannot appear in real
+ * XMPP capabilities.
+ */
+/* Gabble 0.7.x with 16 <= x < 29 omits @creator on <content/> */
+#define WOCKY_QUIRK_OMITS_CONTENT_CREATORS "\x07omits-content-creators"
+/* The Google Webmail client doesn't support some features */
+#define WOCKY_QUIRK_GOOGLE_WEBMAIL_CLIENT "\x07google-webmail-client"
+/* The Android GTalk client needs a quirk for component names */
+#define WOCKY_QUIRK_ANDROID_GTALK_CLIENT "\x07android-gtalk-client"
+
+/* Google's extension for retrieving STUN and relay information from the
+ * server. */
+#define WOCKY_XMPP_NS_GOOGLE_JINGLE_INFO "google:jingleinfo"
+
/* legacy namespaces */
#define WOCKY_JABBER_NS_AUTH \
"jabber:iq:auth"
@@ -157,5 +207,7 @@
#define WOCKY_NS_GOOGLE_SESSION_VIDEO \
"http://www.google.com/session/video"
+#define WOCKY_NS_VCARD_TEMP "vcard-temp"
+#define WOCKY_NS_VCARD_TEMP_UPDATE "vcard-temp:x:update"
#endif /* #ifndef __WOCKY_NAMESPACES_H__ */
diff --git a/wocky/wocky-node.c b/wocky/wocky-node.c
index 4dcaeb8..30d38a7 100644
--- a/wocky/wocky-node.c
+++ b/wocky/wocky-node.c
@@ -1306,7 +1306,12 @@ wocky_node_iter_init (WockyNodeIter *iter,
const gchar *name,
const gchar *ns)
{
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (node != NULL);
+
+ iter->node = node;
iter->pending = node->children;
+ iter->current = NULL;
iter->name = name;
iter->ns = g_quark_from_string (ns);
}
@@ -1330,7 +1335,8 @@ wocky_node_iter_next (WockyNodeIter *iter,
{
WockyNode *ln = (WockyNode *) iter->pending->data;
- iter->pending = g_slist_next (iter->pending);
+ iter->current = iter->pending;
+ iter->pending = g_slist_next (iter->pending);
if (iter->name != NULL && wocky_strdiff (ln->name, iter->name))
continue;
@@ -1344,10 +1350,35 @@ wocky_node_iter_next (WockyNodeIter *iter,
return TRUE;
}
+ iter->current = NULL;
return FALSE;
}
/**
+ * wocky_node_iter_remove:
+ * @iter: an initialized #WockyNodeIter
+ *
+ * Removes and frees the node returned by the last call to
+ * wocky_node_iter_next() from its parent. Can only be called after
+ * wocky_node_iter_next() returned %TRUE, and cannot be called more than once
+ * per successful call to wocky_node_iter_next().
+ */
+void
+wocky_node_iter_remove (WockyNodeIter *iter)
+{
+ g_return_if_fail (iter->node != NULL);
+ g_return_if_fail (iter->current != NULL);
+
+ g_assert (iter->current->data != NULL);
+ wocky_node_free (iter->current->data);
+
+ iter->node->children = g_slist_delete_link (iter->node->children,
+ iter->current);
+
+ iter->current = NULL;
+}
+
+/**
* wocky_node_add_build:
* @node: The node under which to add a new subtree
* @...: the description of the stanza to build,
@@ -1526,17 +1557,21 @@ _wocky_node_copy (WockyNode *node)
* @tree: The node tree to add
*
* Copies the nodes from @tree, and appends them to @node's children.
+ *
+ * Returns: the root of the copy of @tree added to @node.
*/
-void
+WockyNode *
wocky_node_add_node_tree (WockyNode *node, WockyNodeTree *tree)
{
WockyNode *copy;
- g_return_if_fail (node != NULL);
- g_return_if_fail (tree != NULL);
+ g_return_val_if_fail (node != NULL, NULL);
+ g_return_val_if_fail (tree != NULL, NULL);
copy = _wocky_node_copy (wocky_node_tree_get_top_node (tree));
node->children = g_slist_append (node->children, copy);
+
+ return copy;
}
/**
@@ -1546,19 +1581,23 @@ wocky_node_add_node_tree (WockyNode *node, WockyNodeTree *tree)
*
* Copies the nodes from @tree, and inserts them as the first child of @node,
* before any existing children.
+ *
+ * Returns: the root of the copy of @tree added to @node.
*/
-void
+WockyNode *
wocky_node_prepend_node_tree (
WockyNode *node,
WockyNodeTree *tree)
{
WockyNode *copy;
- g_return_if_fail (node != NULL);
- g_return_if_fail (tree != NULL);
+ g_return_val_if_fail (node != NULL, NULL);
+ g_return_val_if_fail (tree != NULL, NULL);
copy = _wocky_node_copy (wocky_node_tree_get_top_node (tree));
node->children = g_slist_prepend (node->children, copy);
+
+ return copy;
}
/**
diff --git a/wocky/wocky-node.h b/wocky/wocky-node.h
index b8b2e5a..bdd7152 100644
--- a/wocky/wocky-node.h
+++ b/wocky/wocky-node.h
@@ -38,6 +38,7 @@ G_BEGIN_DECLS
* @WOCKY_NODE_ATTRIBUTE: A node attribute
* @WOCKY_NODE_XMLNS: A node XML namespace
* @WOCKY_NODE_ASSIGN_TO: a #WockyNode to assign
+ * @WOCKY_NODE_LANGUAGE: <literal>xml:lang</literal> of a node
*
* Tags for building a stanza using wocky_stanza_build() or
* wocky_node_add_build().
@@ -229,7 +230,9 @@ gboolean wocky_node_is_superset (WockyNode *node,
*/
typedef struct {
/*<private>*/
+ WockyNode *node;
GSList *pending;
+ GSList *current;
const gchar *name;
GQuark ns;
} WockyNodeIter;
@@ -242,6 +245,7 @@ void wocky_node_iter_init (WockyNodeIter *iter,
gboolean wocky_node_iter_next (WockyNodeIter *iter,
WockyNode **next);
+void wocky_node_iter_remove (WockyNodeIter *iter);
void wocky_node_add_build (WockyNode *node,
...) G_GNUC_NULL_TERMINATED;
@@ -249,8 +253,8 @@ void wocky_node_add_build (WockyNode *node,
void wocky_node_add_build_va (WockyNode *node,
va_list va);
-void wocky_node_add_node_tree (WockyNode *node, WockyNodeTree *tree);
-void wocky_node_prepend_node_tree (
+WockyNode *wocky_node_add_node_tree (WockyNode *node, WockyNodeTree *tree);
+WockyNode *wocky_node_prepend_node_tree (
WockyNode *node,
WockyNodeTree *tree);
diff --git a/wocky/wocky-openssl.c b/wocky/wocky-openssl.c
index ce60a45..d1b5fd3 100644
--- a/wocky/wocky-openssl.c
+++ b/wocky/wocky-openssl.c
@@ -729,7 +729,7 @@ wocky_tls_session_handshake (WockyTLSSession *session,
}
/* ************************************************************************* */
-/* adding CA certificates lists for peer certificate verification */
+/* adding CA certificates & CRL lists for peer certificate verification */
void
wocky_tls_session_add_ca (WockyTLSSession *session,
@@ -766,6 +766,47 @@ wocky_tls_session_add_ca (WockyTLSSession *session,
DEBUG ("CA '%s' loaded", path);
}
+void
+wocky_tls_session_add_crl (WockyTLSSession *session,
+ const gchar *path)
+{
+ gboolean ok = FALSE;
+
+ if (!g_file_test (path, G_FILE_TEST_EXISTS))
+ {
+ DEBUG ("CRL file or path '%s' not accessible", path);
+ return;
+ }
+
+ if (g_file_test (path, G_FILE_TEST_IS_DIR))
+ {
+ X509_STORE *store = SSL_CTX_get_cert_store (session->ctx);
+ X509_LOOKUP_METHOD *method = X509_LOOKUP_hash_dir ();
+ X509_LOOKUP *lookup = X509_STORE_add_lookup (store, method);
+ DEBUG ("Loading CRL directory");
+ ok = X509_LOOKUP_add_dir (lookup, path, X509_FILETYPE_PEM) == 1;
+ }
+
+ if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
+ {
+ X509_STORE *store = SSL_CTX_get_cert_store (session->ctx);
+ X509_LOOKUP_METHOD *method = X509_LOOKUP_file ();
+ X509_LOOKUP *lookup = X509_STORE_add_lookup (store, method);
+ DEBUG ("Loading CRL file");
+ ok = X509_LOOKUP_load_file (lookup, path, X509_FILETYPE_PEM) == 1;
+ }
+
+ if (!ok)
+ {
+ gulong e, f;
+ for (f = e = ERR_get_error (); e != 0; e = ERR_get_error ())
+ f = e;
+ DEBUG ("'%s' failed: %s\n", path, ERR_error_string (f, NULL));
+ }
+ else
+ DEBUG ("'%s' loaded\n", path);
+}
+
/* ************************************************************************* */
void
@@ -807,36 +848,32 @@ wocky_tls_session_handshake_finish (WockyTLSSession *session,
return g_object_new (WOCKY_TYPE_TLS_CONNECTION, "session", session, NULL);
}
-#define CASELESS_CHARCMP(x, y) \
- ((x) != '\0') && ((y) != '\0') && (toupper (x) == toupper (y))
-
static gboolean
compare_wildcarded_hostname (const char *hostname, const char *certname)
{
DEBUG ("%s ~ %s", hostname, certname);
- /* first diff character */
- for (; CASELESS_CHARCMP (*certname, *hostname); certname++, hostname++);
- /* at end of both strings: simple equality condition met, no wildcard check */
- if (strlen (certname) == 0 && strlen (hostname) == 0)
+ if (g_ascii_strcasecmp (hostname, certname) == 0)
return TRUE;
- if (*certname == '*') /* wildcarded certificate */
+ /* We only allow leading '*.' wildcards. See the final bullet point of XMPP
+ * Core §13.7.1.2.1
+ * <http://xmpp.org/rfcs/rfc6120.html#security-certificates-generation-server>:
+ *
+ * DNS domain names in server certificates MAY contain the wildcard
+ * character '*' as the complete left-most label within the identifier.
+ */
+ if (g_str_has_prefix (certname, "*."))
{
- certname++;
- while (1)
- {
- /* stop at each further char into the hostname and see if it *
- * matches the remainder of the (possibly further wildcarded) *
- * certificate (eg *.*.cam.ac.uk) */
- if (compare_wildcarded_hostname (hostname, certname))
- return TRUE;
- /* came to the end of the hostname or encountered a '.' in *
- * in the middle of the wildcarded region, which is not allowed */
- if (*hostname == '\0' || *hostname == '.')
- break;
- hostname++;
- }
+ const gchar *certname_tail = certname + 2;
+ const gchar *hostname_tail = index (hostname, '.');
+
+ if (hostname_tail == NULL)
+ return FALSE;
+
+ hostname_tail++;
+ DEBUG ("%s ~ %s", hostname_tail, certname_tail);
+ return g_ascii_strcasecmp (hostname_tail, certname_tail) == 0;
}
return FALSE;
@@ -1015,6 +1052,101 @@ wocky_tls_session_get_peers_certificate (WockyTLSSession *session,
return certificates;
}
+static WockyTLSCertStatus
+_cert_status (WockyTLSSession *session,
+ int ssl_code,
+ WockyTLSVerificationLevel level,
+ int old_code)
+{
+ switch (ssl_code)
+ {
+ case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+ case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
+ return WOCKY_TLS_CERT_SIGNER_UNKNOWN;
+ break;
+ case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
+ case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
+ case X509_V_ERR_CERT_SIGNATURE_FAILURE:
+ case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
+ case X509_V_ERR_INVALID_PURPOSE:
+ case X509_V_ERR_CERT_REJECTED:
+ case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+ return WOCKY_TLS_CERT_INVALID;
+ break;
+ case X509_V_ERR_CERT_REVOKED:
+ return WOCKY_TLS_CERT_REVOKED;
+ break;
+ case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
+ case X509_V_ERR_CERT_NOT_YET_VALID:
+ return WOCKY_TLS_CERT_NOT_ACTIVE;
+ break;
+ case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
+ case X509_V_ERR_CERT_HAS_EXPIRED:
+ return WOCKY_TLS_CERT_EXPIRED;
+ break;
+ case X509_V_ERR_OUT_OF_MEM:
+ return WOCKY_TLS_CERT_INTERNAL_ERROR;
+ break;
+ case X509_V_ERR_INVALID_CA:
+ case X509_V_ERR_CERT_UNTRUSTED:
+ case X509_V_ERR_AKID_SKID_MISMATCH:
+ case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
+ case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
+ return WOCKY_TLS_CERT_SIGNER_UNAUTHORISED;
+ break;
+ case X509_V_ERR_PATH_LENGTH_EXCEEDED:
+ return WOCKY_TLS_CERT_MAYBE_DOS;
+ break;
+ case X509_V_ERR_UNABLE_TO_GET_CRL:
+ /* if we are in STRICT mode, being unable to see the CRL is a
+ * terminal condition: in NORMAL or LENIENT we can live with it.
+ * Also, if we re-tried and got the same error, we're just going
+ * to loop indefinitely, so bail out with the original error.
+ * NOTE: 'unable to fetch' a CRL is not the same as CRL invalidated
+ * the certificate, or we'd just turn the CRL checks off when in
+ * NORMAL or LENIENT mode */
+ if (level == WOCKY_TLS_VERIFY_STRICT ||
+ old_code == X509_V_ERR_UNABLE_TO_GET_CRL)
+ {
+ return WOCKY_TLS_CERT_INSECURE;
+ }
+ else
+ {
+ WockyTLSCertStatus status = WOCKY_TLS_CERT_OK;
+ X509_STORE_CTX *xctx = X509_STORE_CTX_new();
+ X509_STORE *store = SSL_CTX_get_cert_store(session->ctx);
+ X509 *cert = SSL_get_peer_certificate (session->ssl);
+ STACK_OF(X509) *chain = SSL_get_peer_cert_chain (session->ssl);
+ long old_flags = store->param->flags;
+ long new_flags = old_flags;
+ DEBUG("No CRL available, but not in strict mode - re-verifying");
+
+ new_flags &= ~(X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
+
+ store->param->flags = new_flags;
+ X509_STORE_CTX_init (xctx, store, cert, chain);
+ X509_STORE_CTX_set_flags (xctx, new_flags);
+
+ if( X509_verify_cert (xctx) < 1 )
+ {
+ int new_code = X509_STORE_CTX_get_error (xctx);
+ status = _cert_status (session, new_code, level, ssl_code);
+ }
+
+ store->param->flags = old_flags;
+ X509_STORE_CTX_free (xctx);
+ X509_free (cert);
+
+ return status;
+ }
+ break;
+ default:
+ return WOCKY_TLS_CERT_UNKNOWN_ERROR;
+ }
+}
+
int
wocky_tls_session_verify_peer (WockyTLSSession *session,
const gchar *peername,
@@ -1076,59 +1208,7 @@ wocky_tls_session_verify_peer (WockyTLSSession *session,
if (rval != X509_V_OK)
{
DEBUG ("cert verification error: %d", rval);
- switch (rval)
- {
- case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
- case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
- case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
- case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
- *status = WOCKY_TLS_CERT_SIGNER_UNKNOWN;
- break;
- case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
- case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
- case X509_V_ERR_CERT_SIGNATURE_FAILURE:
- case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
- case X509_V_ERR_INVALID_PURPOSE:
- case X509_V_ERR_CERT_REJECTED:
- case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
- *status = WOCKY_TLS_CERT_INVALID;
- break;
- case X509_V_ERR_CERT_REVOKED:
- *status = WOCKY_TLS_CERT_REVOKED;
- break;
- case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
- case X509_V_ERR_CERT_NOT_YET_VALID:
- *status = WOCKY_TLS_CERT_NOT_ACTIVE;
- break;
- case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
- case X509_V_ERR_CERT_HAS_EXPIRED:
- *status = WOCKY_TLS_CERT_EXPIRED;
- break;
- case X509_V_ERR_OUT_OF_MEM:
- *status = WOCKY_TLS_CERT_INTERNAL_ERROR;
- break;
- case X509_V_ERR_INVALID_CA:
- case X509_V_ERR_CERT_UNTRUSTED:
- case X509_V_ERR_AKID_SKID_MISMATCH:
- case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
- case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
- *status = WOCKY_TLS_CERT_SIGNER_UNAUTHORISED;
- break;
- case X509_V_ERR_PATH_LENGTH_EXCEEDED:
- *status = WOCKY_TLS_CERT_MAYBE_DOS;
- break;
- case X509_V_ERR_UNABLE_TO_GET_CRL:
- /* if we are in STRICT mode, being unable to see the CRL is a *
- * terminal condition: in NORMAL or LENIENT we can live with it */
- if (level == WOCKY_TLS_VERIFY_STRICT)
- *status = WOCKY_TLS_CERT_INSECURE;
- else
- DEBUG ("ignoring UNABLE_TO_GET_CRL: we're not in strict mode");
-
- break;
- default:
- *status = WOCKY_TLS_CERT_UNKNOWN_ERROR;
- }
+ *status = _cert_status (session, rval, level, X509_V_OK);
/* some conditions are to be ignored when lenient, others still matter */
if (lenient)
diff --git a/wocky/wocky-porter.c b/wocky/wocky-porter.c
index 5b19567..4161fe2 100644
--- a/wocky/wocky-porter.c
+++ b/wocky/wocky-porter.c
@@ -465,18 +465,21 @@ wocky_porter_register_handler_from_by_stanza (WockyPorter *self,
*
* If @from is a bare JID, then the resource of the JID in the from attribute
* will be ignored: In other words, a handler registered against a bare JID
- * will match _all_ stanzas from a JID with the same node and domain:
- * "foo@<!-- -->bar.org" will match
- * "foo@<!-- -->bar.org", "foo@<!-- -->bar.org/moose" and so forth.
+ * will match <emphasis>all</emphasis> stanzas from a JID with the same node
+ * and domain:
+ * <code>"foo&commat;bar.org"</code> will match
+ * <code>"foo&commat;bar.org"</code>,
+ * <code>"foo&commat;bar.org/moose"</code> and so forth.
*
* To register an IQ handler from Juliet for all the Jingle stanzas related
* to one Jingle session:
*
* |[
- * id = wocky_porter_register_handler (porter,
- * WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_NONE, NULL,
- * WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, jingle_cb,
+ * id = wocky_porter_register_handler_from (porter,
+ * WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_NONE,
* "juliet@example.com/Balcony",
+ * WOCKY_PORTER_HANDLER_PRIORITY_NORMAL,
+ * jingle_cb,
* '(', "jingle",
* ':', "urn:xmpp:jingle:1",
* '@', "sid", "my_sid",
diff --git a/wocky/wocky-pubsub-helpers.c b/wocky/wocky-pubsub-helpers.c
index 8da40ba..2202222 100644
--- a/wocky/wocky-pubsub-helpers.c
+++ b/wocky/wocky-pubsub-helpers.c
@@ -123,6 +123,7 @@ wocky_pubsub_make_publish_stanza (
/**
* wocky_pubsub_make_stanza:
* @service: the JID of a PubSub service, or %NULL
+ * @sub_type: #WOCKY_STANZA_SUB_TYPE_SET or #WOCKY_STANZA_SUB_TYPE_GET, as you wish
* @pubsub_ns: the namespace for the &lt;pubsub/&gt; node of the stanza
* @action_name: the action node to add to &lt;pubsub/&gt;
* @pubsub_node: address at which to store a pointer to the &lt;pubsub/&gt; node
@@ -131,7 +132,7 @@ wocky_pubsub_make_publish_stanza (
*
* <!-- -->
*
- * Returns: a new iq[type='set']/pubsub/@action stanza
+ * Returns: a new iq[type=@sub_type]/pubsub/@action stanza
*/
WockyStanza *
wocky_pubsub_make_stanza (
diff --git a/wocky/wocky-pubsub-node-protected.h b/wocky/wocky-pubsub-node-protected.h
index 2d1b537..89833cd 100644
--- a/wocky/wocky-pubsub-node-protected.h
+++ b/wocky/wocky-pubsub-node-protected.h
@@ -61,7 +61,7 @@ GList *wocky_pubsub_node_parse_affiliations (
WockyStanza *wocky_pubsub_node_make_modify_affiliates_stanza (
WockyPubsubNode *self,
- const GList *affiliates,
+ GList *affiliates,
WockyNode **pubsub_node,
WockyNode **affiliations_node);
diff --git a/wocky/wocky-pubsub-node.c b/wocky/wocky-pubsub-node.c
index e2cbcb3..677ecd2 100644
--- a/wocky/wocky-pubsub-node.c
+++ b/wocky/wocky-pubsub-node.c
@@ -902,13 +902,13 @@ wocky_pubsub_node_list_affiliates_finish (
WockyStanza *
wocky_pubsub_node_make_modify_affiliates_stanza (
WockyPubsubNode *self,
- const GList *affiliates,
+ GList *affiliates,
WockyNode **pubsub_node,
WockyNode **affiliations_node)
{
WockyStanza *stanza;
WockyNode *affiliations;
- const GList *l;
+ GList *l;
stanza = pubsub_node_make_action_stanza (self, WOCKY_STANZA_SUB_TYPE_SET,
WOCKY_XMPP_NS_PUBSUB_OWNER, "affiliations", NULL,
@@ -971,7 +971,7 @@ wocky_pubsub_node_make_modify_affiliates_stanza (
void
wocky_pubsub_node_modify_affiliates_async (
WockyPubsubNode *self,
- const GList *affiliates,
+ GList *affiliates,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
diff --git a/wocky/wocky-pubsub-node.h b/wocky/wocky-pubsub-node.h
index 31e35cc..2a57a39 100644
--- a/wocky/wocky-pubsub-node.h
+++ b/wocky/wocky-pubsub-node.h
@@ -26,7 +26,7 @@
#include <glib-object.h>
#include <gio/gio.h>
-#include "wocky-pubsub-node-enumtypes.h"
+#include "wocky-enumtypes.h"
#include "wocky-types.h"
#include "wocky-session.h"
#include "wocky-pubsub-service.h"
@@ -170,7 +170,7 @@ gboolean wocky_pubsub_node_list_affiliates_finish (
void wocky_pubsub_node_modify_affiliates_async (
WockyPubsubNode *self,
- const GList *affiliates,
+ GList *affiliates,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
diff --git a/wocky/wocky-pubsub-service.h b/wocky/wocky-pubsub-service.h
index 676bc04..64ab029 100644
--- a/wocky/wocky-pubsub-service.h
+++ b/wocky/wocky-pubsub-service.h
@@ -26,7 +26,7 @@
#include <glib-object.h>
#include <gio/gio.h>
-#include "wocky-pubsub-service-enumtypes.h"
+#include "wocky-enumtypes.h"
#include "wocky-stanza.h"
#include "wocky-session.h"
#include "wocky-types.h"
diff --git a/wocky/wocky-sasl-auth.c b/wocky/wocky-sasl-auth.c
index 4d55491..70dcbcc 100644
--- a/wocky/wocky-sasl-auth.c
+++ b/wocky/wocky-sasl-auth.c
@@ -292,8 +292,8 @@ auth_failed (WockySaslAuth *sasl, gint code, const gchar *format, ...)
static gboolean
stream_error (WockySaslAuth *sasl, WockyStanza *stanza)
{
- WockyStanzaType type = WOCKY_STANZA_TYPE_NONE;
WockySaslAuthPrivate *priv = sasl->priv;
+ GError *error = NULL;
if (stanza == NULL)
{
@@ -301,13 +301,8 @@ stream_error (WockySaslAuth *sasl, WockyStanza *stanza)
return TRUE;
}
- wocky_stanza_get_type_info (stanza, &type, NULL);
-
- if (type == WOCKY_STANZA_TYPE_STREAM_ERROR)
+ if (wocky_stanza_extract_stream_error (stanza, &error))
{
- GError *error = wocky_xmpp_stream_error_from_node (
- wocky_stanza_get_top_node (stanza));
-
auth_failed (sasl, WOCKY_AUTH_ERROR_STREAM, "%s: %s",
wocky_enum_to_nick (WOCKY_TYPE_XMPP_STREAM_ERROR, error->code),
error->message);
diff --git a/wocky/wocky-sasl-scram.c b/wocky/wocky-sasl-scram.c
index 51c703b..fc28553 100644
--- a/wocky/wocky-sasl-scram.c
+++ b/wocky/wocky-sasl-scram.c
@@ -530,7 +530,7 @@ scram_handle_server_final_message (WockySaslScram *self,
{
gchar attr, *value = NULL;
- if (!scram_get_next_attr_value (&message, &attr, &value) && attr != 'v')
+ if (!scram_get_next_attr_value (&message, &attr, &value) || attr != 'v')
goto invalid;
if (!scram_check_server_verification (self, value))
diff --git a/wocky/wocky-stanza.c b/wocky/wocky-stanza.c
index 6215727..a44fe6a 100644
--- a/wocky/wocky-stanza.c
+++ b/wocky/wocky-stanza.c
@@ -49,9 +49,10 @@ typedef struct
WockyStanzaType type;
const gchar *name;
const gchar *ns;
+ GQuark ns_q;
} StanzaTypeName;
-static const StanzaTypeName type_names[NUM_WOCKY_STANZA_TYPE] =
+static StanzaTypeName type_names[NUM_WOCKY_STANZA_TYPE] =
{
{ WOCKY_STANZA_TYPE_NONE, NULL,
WOCKY_XMPP_NS_JABBER_CLIENT },
@@ -80,6 +81,16 @@ static const StanzaTypeName type_names[NUM_WOCKY_STANZA_TYPE] =
{ WOCKY_STANZA_TYPE_UNKNOWN, NULL, NULL },
};
+static void
+fill_in_namespace_quarks (void)
+{
+ int i;
+
+ /* We skip the first entry as it's NONE */
+ for (i = 1; type_names[i].type != WOCKY_STANZA_TYPE_UNKNOWN; i++)
+ type_names[i].ns_q = g_quark_from_static_string (type_names[i].ns);
+}
+
typedef struct
{
WockyStanzaSubType sub_type;
@@ -147,6 +158,8 @@ wocky_stanza_class_init (WockyStanzaClass *wocky_stanza_class)
object_class->dispose = wocky_stanza_dispose;
object_class->finalize = wocky_stanza_finalize;
+
+ fill_in_namespace_quarks ();
}
static void
@@ -299,10 +312,9 @@ wocky_stanza_new_with_sub_type (WockyStanzaType type,
* @...: the description of the stanza to build,
* terminated with %NULL
*
- * Build a XMPP stanza from a list of arguments.
- * Example:
+ * Build a XMPP stanza from a list of arguments. For example, the following invocation:
*
- * <example><programlisting>
+ * |[
* wocky_stanza_build (
* WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_NONE,
* "alice@<!-- -->collabora.co.uk", "bob@<!-- -->collabora.co.uk",
@@ -314,8 +326,11 @@ wocky_stanza_new_with_sub_type (WockyStanzaType type,
* WOCKY_NODE_END,
* WOCKY_NODE_END,
* NULL);
- * <!-- -->
- * /<!-- -->* produces
+ * ]|
+ *
+ * produces this stanza:
+ *
+ * |[
* &lt;message from='alice@<!-- -->collabora.co.uk' to='bob@<!-- -->collabora.co.uk'&gt;
* &lt;html xmlns='http://www.w3.org/1999/xhtml'&gt;
* &lt;body textcolor='red'&gt;
@@ -323,14 +338,13 @@ wocky_stanza_new_with_sub_type (WockyStanzaType type,
* &lt;/body&gt;
* &lt;/html&gt;
* &lt;/message&gt;
- * *<!-- -->/
- * </programlisting></example>
+ * ]|
*
* You may optionally use mnemonic ASCII characters in place of the build tags,
* to better reflect the structure of the stanza in C source. For example, the
* above stanza could be written as:
*
- * <example><programlisting>
+ * |[
* wocky_stanza_build (
* WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_NONE,
* "alice@<!-- -->collabora.co.uk", "bob@<!-- -->collabora.co.uk",
@@ -340,7 +354,7 @@ wocky_stanza_new_with_sub_type (WockyStanzaType type,
* ')',
* ')'
* NULL);
- * </programlisting></example>
+ * ]|
*
* Returns: a new stanza object
*/
@@ -418,8 +432,10 @@ wocky_stanza_build_va (WockyStanzaType type,
}
static WockyStanzaType
-get_type_from_name (const gchar *name)
+get_type_from_node (WockyNode *node)
{
+ const gchar *name = node->name;
+ GQuark ns = node->ns;
guint i;
if (name == NULL)
@@ -429,6 +445,7 @@ get_type_from_name (const gchar *name)
for (i = 1; i < WOCKY_STANZA_TYPE_UNKNOWN; i++)
{
if (type_names[i].name != NULL &&
+ ns == type_names[i].ns_q &&
strcmp (name, type_names[i].name) == 0)
{
return type_names[i].type;
@@ -464,15 +481,32 @@ wocky_stanza_get_type_info (WockyStanza *stanza,
WockyStanzaType *type,
WockyStanzaSubType *sub_type)
{
+ WockyNode *top_node;
+
g_return_if_fail (stanza != NULL);
- g_assert (wocky_stanza_get_top_node (stanza) != NULL);
+
+ top_node = wocky_stanza_get_top_node (stanza);
+ g_assert (top_node != NULL);
if (type != NULL)
- *type = get_type_from_name (wocky_stanza_get_top_node (stanza)->name);
+ *type = get_type_from_node (top_node);
if (sub_type != NULL)
- *sub_type = get_sub_type_from_name (wocky_node_get_attribute (
- wocky_stanza_get_top_node (stanza), "type"));
+ *sub_type = get_sub_type_from_name (
+ wocky_node_get_attribute (top_node, "type"));
+}
+
+gboolean
+wocky_stanza_has_type (WockyStanza *stanza,
+ WockyStanzaType expected_type)
+{
+ WockyStanzaType actual_type;
+
+ g_return_val_if_fail (WOCKY_IS_STANZA (stanza), FALSE);
+
+ wocky_stanza_get_type_info (stanza, &actual_type, NULL);
+
+ return expected_type == actual_type;
}
static WockyStanza *
@@ -689,11 +723,7 @@ gboolean
wocky_stanza_extract_stream_error (WockyStanza *stanza,
GError **stream_error)
{
- WockyStanzaType type;
-
- wocky_stanza_get_type_info (stanza, &type, NULL);
-
- if (type != WOCKY_STANZA_TYPE_STREAM_ERROR)
+ if (!wocky_stanza_has_type (stanza, WOCKY_STANZA_TYPE_STREAM_ERROR))
return FALSE;
g_propagate_error (stream_error,
diff --git a/wocky/wocky-stanza.h b/wocky/wocky-stanza.h
index 9a9c4b0..bcca670 100644
--- a/wocky/wocky-stanza.h
+++ b/wocky/wocky-stanza.h
@@ -170,6 +170,8 @@ WockyStanza * wocky_stanza_build_to_contact (WockyStanzaType type,
void wocky_stanza_get_type_info (WockyStanza *stanza,
WockyStanzaType *type, WockyStanzaSubType *sub_type);
+gboolean wocky_stanza_has_type (WockyStanza *stanza,
+ WockyStanzaType expected_type);
const gchar *wocky_stanza_get_from (WockyStanza *self);
const gchar *wocky_stanza_get_to (WockyStanza *self);
diff --git a/wocky/wocky-tls-connector.c b/wocky/wocky-tls-connector.c
index e3ab3cb..5fa1fda 100644
--- a/wocky/wocky-tls-connector.c
+++ b/wocky/wocky-tls-connector.c
@@ -92,7 +92,7 @@ wocky_tls_connector_set_property (GObject *object,
{
case PROP_HANDLER:
if (g_value_get_object (value) == NULL)
- self->priv->handler = wocky_tls_handler_new (TRUE);
+ self->priv->handler = wocky_tls_handler_new (FALSE);
else
self->priv->handler = g_value_dup_object (value);
break;
@@ -175,13 +175,28 @@ add_ca (gpointer data,
}
static void
+add_crl (gpointer data,
+ gpointer user_data)
+{
+ WockyTLSSession *session = user_data;
+ const gchar *path = data;
+
+ wocky_tls_session_add_crl (session, path);
+}
+
+
+
+static void
prepare_session (WockyTLSConnector *self)
{
GSList *cas;
+ GSList *crl;
cas = wocky_tls_handler_get_cas (self->priv->handler);
+ crl = wocky_tls_handler_get_crl (self->priv->handler);
g_slist_foreach (cas, add_ca, self->priv->session);
+ g_slist_foreach (crl, add_crl, self->priv->session);
}
static void
diff --git a/wocky/wocky-tls-handler.c b/wocky/wocky-tls-handler.c
index a65f3e1..b3ff78f 100644
--- a/wocky/wocky-tls-handler.c
+++ b/wocky/wocky-tls-handler.c
@@ -50,6 +50,7 @@ struct _WockyTLSHandlerPrivate {
gboolean ignore_ssl_errors;
GSList *cas;
+ GSList *crl;
};
static void
@@ -101,6 +102,13 @@ wocky_tls_handler_finalize (GObject *object)
g_slist_free (self->priv->cas);
}
+ if (self->priv->crl != NULL)
+ {
+ g_slist_foreach (self->priv->crl, (GFunc) g_free, NULL);
+ g_slist_free (self->priv->crl);
+ }
+
+
G_OBJECT_CLASS (wocky_tls_handler_parent_class)->finalize (object);
}
@@ -126,7 +134,7 @@ wocky_tls_handler_class_init (WockyTLSHandlerClass *klass)
* insecurity/expiry etc).
*/
pspec = g_param_spec_boolean ("ignore-ssl-errors", "ignore-ssl-errors",
- "Whether recoverable TLS errors should be ignored", TRUE,
+ "Whether recoverable TLS errors should be ignored", FALSE,
(G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (oclass, PROP_TLS_INSECURE_OK, pspec);
}
@@ -136,6 +144,10 @@ wocky_tls_handler_init (WockyTLSHandler *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, WOCKY_TYPE_TLS_HANDLER,
WockyTLSHandlerPrivate);
+
+#ifdef GTLS_SYSTEM_CA_CERTIFICATES
+ wocky_tls_handler_add_ca (self, GTLS_SYSTEM_CA_CERTIFICATES);
+#endif
}
static void
@@ -286,18 +298,15 @@ wocky_tls_handler_new (gboolean ignore_ssl_errors)
* @self: a #WockyTLSHandler instance
* @path: a path to a directory or file containing PEM encoded CA certificates
*
- * Sensible default paths (under Debian derived distributions) are:
- *
- * * for gnutls: /etc/ssl/certs/ca-certificates.crt
- * * for openssl: /etc/ssl/certs
- *
- * Certificates my also be found under /usr/share/ca-certificates/...
- * if the user wishes to pick and choose which CAs to use.
+ * Adds a single CA certificate, or directory full of CA certificates, to the
+ * set used to check certificates. By default, Wocky will check the system-wide
+ * certificate directory (as determined at compile time), so you need only add
+ * additional CA paths if you want to trust additional CAs.
*
- * Returns: a #gboolean indicating whether the path was resolved.
- * Does not indicate that there was actually a file or directory there
- * or that any CAs were actually found. The CAs won't actually be loaded
- * until just before the TLS session setup is attempted.
+ * Returns: %TRUE if @path could be resolved to an absolute path. Note that
+ * this does not indicate that there was actually a file or directory there or
+ * that any CAs were actually found. The CAs won't actually be loaded until
+ * just before the TLS session setup is attempted.
*/
gboolean
wocky_tls_handler_add_ca (WockyTLSHandler *self,
@@ -311,6 +320,62 @@ wocky_tls_handler_add_ca (WockyTLSHandler *self,
return abspath != NULL;
}
+
+/**
+ * wocky_tls_handler_forget_cas:
+ * @self: a #WockyTLSHandler instance
+ *
+ * Removes all known locations for CA certificates, including the system-wide
+ * certificate directory and any paths added by previous calls to
+ * wocky_tls_handler_add_ca(). This is only useful if you want Wocky to
+ * distrust your system CAs for some reason.
+ */
+void
+wocky_tls_handler_forget_cas (WockyTLSHandler *self)
+{
+ g_slist_free_full (self->priv->cas, g_free);
+ self->priv->cas = NULL;
+}
+
+
+/**
+ * wocky_tls_handler_add_crl:
+ * @self: a #WockyTLSHandler instance
+ * @path: a path to a directory or file containing PEM encoded CRL certificates
+ *
+ * Adds a single certificate revocation list file, or a directory of CRLs, to
+ * the set used to check certificates. Unlike for CA certificates, there is
+ * typically no good default path, so no CRLs are used by default. The path to
+ * use depends on the CRL-management software you use; `dirmngr`
+ * (for example) will cache CRLs in `/var/cache/dirmngr/crls.d`.
+ *
+ * Returns: %TRUE if @path could be resolved to an absolute path. Note that
+ * this does not indicate that there was actually a file or directory there or
+ * that any CRLs were actually found. The CRLs won't actually be loaded until
+ * just before the TLS session setup is attempted.
+ */
+gboolean
+wocky_tls_handler_add_crl (WockyTLSHandler *self,
+ const gchar *path)
+{
+ gchar *abspath = wocky_absolutize_path (path);
+
+ if (abspath != NULL)
+ self->priv->crl = g_slist_prepend (self->priv->crl, abspath);
+
+ return abspath != NULL;
+}
+
+
+/**
+ * wocky_tls_handler_get_cas:
+ * @self: a #WockyTLSHandler instance
+ *
+ * Gets the CA certificate search path, including any extra paths added with
+ * wocky_tls_handler_add_ca().
+ *
+ * Returns: (transfer none) (element-type utf8): the paths to search for CA certificates.
+ */
GSList *
wocky_tls_handler_get_cas (WockyTLSHandler *self)
{
@@ -318,3 +383,21 @@ wocky_tls_handler_get_cas (WockyTLSHandler *self)
return self->priv->cas;
}
+
+
+/**
+ * wocky_tls_handler_get_crl:
+ * @self: a #WockyTLSHandler instance
+ *
+ * Gets the CRL search path, consisting of all paths added with
+ * wocky_tls_handler_add_crl().
+ *
+ * Returns: (transfer none) (element-type utf8): the CRL search path.
+ */
+GSList *
+wocky_tls_handler_get_crl (WockyTLSHandler *self)
+{
+ g_assert (WOCKY_IS_TLS_HANDLER (self));
+
+ return self->priv->crl;
+}
diff --git a/wocky/wocky-tls-handler.h b/wocky/wocky-tls-handler.h
index b5f7a9a..deef6d6 100644
--- a/wocky/wocky-tls-handler.h
+++ b/wocky/wocky-tls-handler.h
@@ -104,8 +104,12 @@ gboolean wocky_tls_handler_verify_finish (WockyTLSHandler *self,
gboolean wocky_tls_handler_add_ca (WockyTLSHandler *self,
const gchar *path);
+void wocky_tls_handler_forget_cas (WockyTLSHandler *self);
+
+gboolean wocky_tls_handler_add_crl (WockyTLSHandler *self, const gchar *path);
GSList *wocky_tls_handler_get_cas (WockyTLSHandler *self);
+GSList *wocky_tls_handler_get_crl (WockyTLSHandler *self);
G_END_DECLS
diff --git a/wocky/wocky-tls.c b/wocky/wocky-tls.c
index f4e37c6..c517a3f 100644
--- a/wocky/wocky-tls.c
+++ b/wocky/wocky-tls.c
@@ -568,6 +568,62 @@ wocky_tls_session_add_ca (WockyTLSSession *session,
}
}
+void
+wocky_tls_session_add_crl (WockyTLSSession *session, const gchar *crl_path)
+{
+ int n = 0;
+ struct stat target;
+
+ DEBUG ("adding CRL CERT path '%s'", (gchar *) crl_path);
+
+ if (stat (crl_path, &target) != 0)
+ {
+ DEBUG ("CRL file '%s': stat failed)", crl_path);
+ return;
+ }
+
+ if (S_ISDIR (target.st_mode))
+ {
+ DIR *dir;
+ struct dirent *entry;
+
+ if ((dir = opendir (crl_path)) == NULL)
+ return;
+
+ for (entry = readdir (dir); entry != NULL; entry = readdir (dir))
+ {
+ struct stat file;
+ gchar *path = g_build_path ("/", crl_path, entry->d_name, NULL);
+
+ if ((stat (path, &file) == 0) && S_ISREG (file.st_mode))
+ {
+ int x = gnutls_certificate_set_x509_crl_file (
+ session->gnutls_cert_cred, path, GNUTLS_X509_FMT_PEM);
+
+ if (x < 0)
+ DEBUG ("Error loading %s: %d %s", path, x, gnutls_strerror (x));
+ else
+ n += x;
+ }
+
+ g_free (path);
+ }
+
+ DEBUG ("+ %s: %d certs from dir", crl_path, n);
+ closedir (dir);
+ }
+ else if (S_ISREG (target.st_mode))
+ {
+ n = gnutls_certificate_set_x509_trust_file (session->gnutls_cert_cred,
+ crl_path, GNUTLS_X509_FMT_PEM);
+
+ if (n < 0)
+ DEBUG ("Error loading '%s': %d %s", crl_path, n, gnutls_strerror(n));
+ else
+ DEBUG ("+ %s: %d certs from file", crl_path, n);
+ }
+}
+
/* ************************************************************************* */
void
@@ -652,6 +708,73 @@ wocky_tls_session_get_peers_certificate (WockyTLSSession *session,
return certificates;
}
+static inline gboolean
+contains_illegal_wildcard (const char *name, int size)
+{
+ if (name[0] == '*' && name[1] == '.')
+ {
+ name += 2;
+ size -= 2;
+ }
+
+ if (memchr (name, '*', size) != NULL)
+ return TRUE;
+
+ return FALSE;
+}
+
+#define OID_X520_COMMON_NAME "2.5.4.3"
+
+static gboolean
+cert_names_are_valid (gnutls_x509_crt_t cert)
+{
+ char name[256];
+ size_t size;
+ gboolean found = FALSE;
+ int type = 0;
+ int i = 0;
+
+ /* GNUTLS allows wildcards anywhere within the certificate name, but XMPP only
+ * permits a single leading "*.".
+ */
+ for (i = 0; type >= 0; i++)
+ {
+ size = sizeof (name);
+ type = gnutls_x509_crt_get_subject_alt_name (cert, i, name, &size, NULL);
+
+ switch (type)
+ {
+ case GNUTLS_SAN_DNSNAME:
+ case GNUTLS_SAN_IPADDRESS:
+ found = TRUE;
+ if (contains_illegal_wildcard (name, size))
+ return FALSE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ size = sizeof (name);
+
+ /* cert has no names at all? bizarro! */
+ if (gnutls_x509_crt_get_dn_by_oid (cert, OID_X520_COMMON_NAME, 0,
+ 0, name, &size) < 0)
+ return FALSE;
+
+ found = TRUE;
+
+ if (contains_illegal_wildcard (name, size))
+ return FALSE;
+
+ }
+
+ /* found a name, wasn't a duff wildcard */
+ return found;
+}
+
int
wocky_tls_session_verify_peer (WockyTLSSession *session,
const gchar *peername,
@@ -754,9 +877,16 @@ wocky_tls_session_verify_peer (WockyTLSSession *session,
if (peername != NULL)
{
- rval = gnutls_x509_crt_check_hostname (x509, peername);
- DEBUG ("gnutls_x509_crt_check_hostname: %s -> %d",
- peername, rval);
+ if (!cert_names_are_valid (x509))
+ {
+ rval = 0;
+ }
+ else
+ {
+ rval = gnutls_x509_crt_check_hostname (x509, peername);
+ DEBUG ("gnutls_x509_crt_check_hostname: %s -> %d",
+ peername, rval);
+ }
}
else
{
@@ -765,17 +895,24 @@ wocky_tls_session_verify_peer (WockyTLSSession *session,
if (rval == 0 && extra_identities != NULL)
{
- gint i;
-
- for (i = 0; extra_identities[i] != NULL; i++)
+ if (!cert_names_are_valid (x509))
{
- rval = gnutls_x509_crt_check_hostname (x509,
- extra_identities[i]);
- DEBUG ("gnutls_x509_crt_check_hostname: %s -> %d",
- extra_identities[i], rval);
-
- if (rval != 0)
- break;
+ rval = 0;
+ }
+ else
+ {
+ gint i;
+
+ for (i = 0; extra_identities[i] != NULL; i++)
+ {
+ rval = gnutls_x509_crt_check_hostname (x509,
+ extra_identities[i]);
+ DEBUG ("gnutls_x509_crt_check_hostname: %s -> %d",
+ extra_identities[i], rval);
+
+ if (rval != 0)
+ break;
+ }
}
}
diff --git a/wocky/wocky-tls.h b/wocky/wocky-tls.h
index a974c0c..a2f72e7 100644
--- a/wocky/wocky-tls.h
+++ b/wocky/wocky-tls.h
@@ -31,7 +31,7 @@
#include <gio/gio.h>
-#include "wocky-tls-enumtypes.h"
+#include "wocky-enumtypes.h"
#define WOCKY_TYPE_TLS_CONNECTION (wocky_tls_connection_get_type ())
#define WOCKY_TYPE_TLS_SESSION (wocky_tls_session_get_type ())
@@ -108,6 +108,7 @@ wocky_tls_session_handshake_finish (WockyTLSSession *session,
GError **error);
void wocky_tls_session_add_ca (WockyTLSSession *session, const gchar *path);
+void wocky_tls_session_add_crl (WockyTLSSession *session, const gchar *path);
WockyTLSSession *wocky_tls_session_new (GIOStream *stream);
diff --git a/wocky/wocky-uninstalled.pc.in b/wocky/wocky-uninstalled.pc.in
index fb286a9..429a256 100644
--- a/wocky/wocky-uninstalled.pc.in
+++ b/wocky/wocky-uninstalled.pc.in
@@ -6,7 +6,6 @@ abs_top_builddir=@abs_top_builddir@
Name: Wocky (uninstalled copy)
Description: XMPP library
Version: @VERSION@
-Requires: pkg-config >= 0.21
Requires.private: glib-2.0 >= 2.16, gobject-2.0 >= 2.16, gio-2.0 >= 2.26
Libs: ${abs_top_builddir}/wocky/libwocky.la
Cflags: -I${abs_top_srcdir} -I${abs_top_builddir} -I${abs_top_builddir}/wocky
diff --git a/wocky/wocky-utils.c b/wocky/wocky-utils.c
index 651d2d2..c3845b2 100644
--- a/wocky/wocky-utils.c
+++ b/wocky/wocky-utils.c
@@ -94,19 +94,20 @@ validate_jid_domain (const gchar *domain)
/**
* wocky_decode_jid:
* @jid: a JID
- * @node: address to which return the username/room part of the JID
- * @domain: address to which return the server/service part of the JID
- * @resource: address to which return the resource/nick part of the JID
+ * @node: (allow-none): address to store the normalised localpart of the JID
+ * @domain: (allow-none): address to store the normalised domainpart of the JID
+ * @resource: address to store the resourcepart of the JID
*
- * If the JID is valid, returns TRUE and sets the caller's
- * node/domain/resource pointers if they are not NULL. The node and resource
- * pointers will be set to NULL if the respective part is not present in the
- * JID. The node and domain are lower-cased because the Jabber protocol treats
- * them case-insensitively.
+ * If @jid is valid, returns %TRUE and sets the caller's @node, @domain and
+ * @resource pointers. @node and @resource will be set to %NULL if the
+ * respective part is not present in @jid. If @jid is invalid, sets @node,
+ * @domain and @resource to %NULL and returns %FALSE.
*
- * XXX: Do nodeprep/resourceprep and length checking.
- *
- * See RFC 3920 §3.
+ * In theory, the returned parts will be normalised as specified in <ulink
+ * url='http://xmpp.org/rfcs/rfc6122.html'>RFC 6122 (XMPP Address
+ * Format)</ulink>; in practice, Wocky does not fully implement the
+ * normalisation and validation algorithms. FIXME: Do nodeprep/resourceprep and
+ * length checking.
*
* Returns: %TRUE if the JID is valid
*/
@@ -234,13 +235,14 @@ strlen0 (const gchar *s)
/**
* wocky_compose_jid:
- * @node: the node part of a JID, possibly empty or %NULL
+ * @node: (allow-none): the node part of a JID, possibly empty or %NULL
* @domain: the non-%NULL domain part of a JID
- * @resource: the resource part of a JID, possibly empty or %NULL
+ * @resource: (allow-none): the resource part of a JID, possibly empty or %NULL
*
* Composes a JID from its parts. If @node is empty or %NULL, the '&commat;'
* separator is also omitted; if @resource is empty or %NULL, the '/' separator
- * is also omitted.
+ * is also omitted. @node and @domain are assumed to have already been
+ * normalised.
*
* Returns: a JID constructed from @node, @domain and @resource
*/
@@ -700,10 +702,10 @@ wocky_absolutize_path (const gchar *path)
GList *
wocky_list_deep_copy (GBoxedCopyFunc copy,
- const GList *items)
+ GList *items)
{
GList *ret = NULL;
- const GList *l;
+ GList *l;
g_return_val_if_fail (copy != NULL, NULL);
diff --git a/wocky/wocky-utils.h b/wocky/wocky-utils.h
index 652c038..2a2eb1d 100644
--- a/wocky/wocky-utils.h
+++ b/wocky/wocky-utils.h
@@ -67,7 +67,7 @@ const gchar *wocky_enum_to_nick (GType enum_type, gint value);
gchar *wocky_absolutize_path (const gchar *path);
-GList *wocky_list_deep_copy (GBoxedCopyFunc copy, const GList *items);
+GList *wocky_list_deep_copy (GBoxedCopyFunc copy, GList *items);
GString *wocky_g_string_dup (const GString *str);
diff --git a/wocky/wocky-xmpp-connection.c b/wocky/wocky-xmpp-connection.c
index 9a4ee68..6097763 100644
--- a/wocky/wocky-xmpp-connection.c
+++ b/wocky/wocky-xmpp-connection.c
@@ -23,7 +23,7 @@
* @title: WockyXmppConnection
* @short_description: Low-level XMPP connection.
*
- * Sends and receives #WockyStanzas from an underlying GIOStream.
+ * Sends and receives #WockyStanza<!-- -->s from an underlying #GIOStream.
*/
#ifdef HAVE_CONFIG_H
diff --git a/wocky/wocky-xmpp-error.c b/wocky/wocky-xmpp-error.c
index 42a2c8f..485bfbf 100644
--- a/wocky/wocky-xmpp-error.c
+++ b/wocky/wocky-xmpp-error.c
@@ -215,6 +215,16 @@ static const XmppErrorSpec xmpp_errors[NUM_WOCKY_XMPP_ERRORS] =
WOCKY_XMPP_ERROR_TYPE_CANCEL,
{ 503, 502, 510, },
},
+
+ /* policy-violation */
+ {
+ "the entity has violated some local service policy (e.g., a message "
+ "contains words that are prohibited by the service)",
+ /* TODO: should support either MODIFY or WAIT depending on the policy
+ * being violated */
+ WOCKY_XMPP_ERROR_TYPE_MODIFY,
+ { 406, 0, },
+ },
};
GQuark
@@ -451,9 +461,17 @@ wocky_xmpp_error_extract (WockyNode *error,
if (type_attr != NULL &&
wocky_enum_from_nick (WOCKY_TYPE_XMPP_ERROR_TYPE, type_attr, &type_i))
- *type = type_i;
+ {
+ *type = type_i;
+ /* Don't let the xmpp_error_from_code() path below clobber the valid
+ * type we found.
+ */
+ type = NULL;
+ }
else
- *type = WOCKY_XMPP_ERROR_TYPE_CANCEL;
+ {
+ *type = WOCKY_XMPP_ERROR_TYPE_CANCEL;
+ }
}
for (l = error->children; l != NULL; l = g_slist_next (l))
diff --git a/wocky/wocky-xmpp-error.h b/wocky/wocky-xmpp-error.h
index 826efb2..b30a304 100644
--- a/wocky/wocky-xmpp-error.h
+++ b/wocky/wocky-xmpp-error.h
@@ -27,7 +27,7 @@
#include <glib.h>
#include <glib-object.h>
-#include "wocky-xmpp-error-enumtypes.h"
+#include "wocky-enumtypes.h"
#include "wocky-node.h"
/**
@@ -55,7 +55,7 @@ typedef enum
/**
* WockyXmppError:
- * @WOCKY_XMPP_ERROR_UNDEFINED_CONDITION: he error condition is not one
+ * @WOCKY_XMPP_ERROR_UNDEFINED_CONDITION: the error condition is not one
* of those defined by the other conditions in this list
* @WOCKY_XMPP_ERROR_REDIRECT: the recipient or server is redirecting
* requests for this information to another entity
@@ -72,10 +72,12 @@ typedef enum
* provided improper credentials
* @WOCKY_XMPP_ERROR_PAYMENT_REQUIRED: the requesting entity is not
* authorized to access the requested service because payment is
- * required
+ * required. This code is no longer defined in RFC 6120, the current version
+ * of XMPP Core. It's preserved here for interoperability, but new
+ * applications should not send it.
* @WOCKY_XMPP_ERROR_FORBIDDEN: the requesting entity does not possess
* the required permissions to perform the action
- * @WOCKY_XMPP_ERROR_ITEM_NOT_FOUND: he addressed JID or item requested
+ * @WOCKY_XMPP_ERROR_ITEM_NOT_FOUND: the addressed JID or item requested
* cannot be found
* @WOCKY_XMPP_ERROR_RECIPIENT_UNAVAILABLE: the intended recipient is
* temporarily unavailable
@@ -109,8 +111,16 @@ typedef enum
* be processed
* @WOCKY_XMPP_ERROR_SERVICE_UNAVAILABLE: the server or recipient does
* not currently provide the requested service
+ * @WOCKY_XMPP_ERROR_POLICY_VIOLATION: the entity has violated some local
+ * service policy (e.g., a message contains words that are prohibited by the
+ * service) and the server MAY choose to specify the policy as the text of
+ * the error or in an application-specific condition element; the associated
+ * error type SHOULD be %WOCKY_XMPP_ERROR_TYPE_MODIFY or
+ * %WOCKY_XMPP_ERROR_TYPE_WAIT depending on the policy being violated.
*
- * Possible XMPP stream errors, as defined by RFC 3920 §9.3.3.
+ * Possible stanza-level errors, as defined by <ulink
+ * url='http://xmpp.org/rfcs/rfc6120.html#stanzas-error-conditions'>RFC 6210
+ * §8.3.3</ulink>.
*/
/*< prefix=WOCKY_XMPP_ERROR >*/
@@ -152,6 +162,8 @@ typedef enum {
WOCKY_XMPP_ERROR_SERVICE_UNAVAILABLE, /* 502, 503, 510 */
+ WOCKY_XMPP_ERROR_POLICY_VIOLATION,
+
/*< private >*/
NUM_WOCKY_XMPP_ERRORS /*< skip >*/ /* don't want this in the GEnum */
} WockyXmppError;
diff --git a/wocky/wocky-xmpp-reader.c b/wocky/wocky-xmpp-reader.c
index 2835401..f0eaa92 100644
--- a/wocky/wocky-xmpp-reader.c
+++ b/wocky/wocky-xmpp-reader.c
@@ -51,6 +51,7 @@
/* properties */
enum {
PROP_STREAMING_MODE = 1,
+ PROP_DEFAULT_NAMESPACE,
PROP_TO,
PROP_FROM,
PROP_VERSION,
@@ -124,6 +125,7 @@ struct _WockyXmppReaderPrivate
gboolean dispose_has_run;
GError *error /* defeat the coding style checker... */;
gboolean stream_mode;
+ gchar *default_namespace;
GQueue *stanzas;
WockyXmppReaderState state;
};
@@ -242,6 +244,8 @@ wocky_xmpp_reader_class_init (WockyXmppReaderClass *wocky_xmpp_reader_class)
g_type_class_add_private (wocky_xmpp_reader_class,
sizeof (WockyXmppReaderPrivate));
+ wocky_xmpp_reader_class->stream_element_name = "stream";
+ wocky_xmpp_reader_class->stream_element_ns = WOCKY_XMPP_NS_STREAM;
object_class->constructed = wocky_xmpp_reader_constructed;
object_class->dispose = wocky_xmpp_reader_dispose;
@@ -257,6 +261,14 @@ wocky_xmpp_reader_class_init (WockyXmppReaderClass *wocky_xmpp_reader_class)
g_object_class_install_property (object_class, PROP_STREAMING_MODE,
param_spec);
+ param_spec = g_param_spec_string ("default-namespace", "default namespace",
+ "The default namespace for the root element of the document. "
+ "Only meaningful if streaming-mode is FALSE.",
+ "",
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DEFAULT_NAMESPACE,
+ param_spec);
+
param_spec = g_param_spec_string ("to", "to",
"to attribute in the xml stream opening",
NULL,
@@ -337,6 +349,14 @@ wocky_xmpp_reader_set_property (GObject *object,
case PROP_STREAMING_MODE:
priv->stream_mode = g_value_get_boolean (value);
break;
+ case PROP_DEFAULT_NAMESPACE:
+ g_free (priv->default_namespace);
+ priv->default_namespace = g_value_dup_string (value);
+
+ if (priv->default_namespace == NULL)
+ priv->default_namespace = g_strdup ("");
+
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -357,6 +377,9 @@ wocky_xmpp_reader_get_property (GObject *object,
case PROP_STREAMING_MODE:
g_value_set_boolean (value, priv->stream_mode);
break;
+ case PROP_DEFAULT_NAMESPACE:
+ g_value_set_string (value, priv->default_namespace);
+ break;
case PROP_FROM:
g_value_set_string (value, priv->from);
break;
@@ -379,7 +402,7 @@ wocky_xmpp_reader_get_property (GObject *object,
}
/**
- * wocky_xmpp_reader_new
+ * wocky_xmpp_reader_new:
*
* Convenience function to create a new #WockyXmppReader.
*
@@ -392,7 +415,7 @@ wocky_xmpp_reader_new (void)
}
/**
- * wocky_xmpp_reader_new_no_stream
+ * wocky_xmpp_reader_new_no_stream:
*
* Convenience function to create a new #WockyXmppReader that has streaming
* mode disabled.
@@ -407,6 +430,25 @@ wocky_xmpp_reader_new_no_stream (void)
NULL);
}
+/**
+ * wocky_xmpp_reader_new_no_stream_ns:
+ * @default_namespace: default XML namespace to apply to the top-level element
+ *
+ * Create a new #WockyXmppReader, with #WockyXmppReader:streaming-mode disabled
+ * and the specified #WockyXmppReader:default-namespace.
+ *
+ * Returns: (transfer full): a new #WockyXmppReader in non-streaming mode.
+ */
+WockyXmppReader *
+wocky_xmpp_reader_new_no_stream_ns (
+ const gchar *default_namespace)
+{
+ return g_object_new (WOCKY_TYPE_XMPP_READER,
+ "streaming-mode", FALSE,
+ "default-namespace", default_namespace,
+ NULL);
+}
+
static void
handle_stream_open (
WockyXmppReader *self,
@@ -416,15 +458,19 @@ handle_stream_open (
int nb_attributes,
const xmlChar **attributes)
{
+ WockyXmppReaderClass *klass = WOCKY_XMPP_READER_GET_CLASS (self);
WockyXmppReaderPrivate *priv = self->priv;
int i;
- if (wocky_strdiff ("stream", localname)
- || wocky_strdiff (WOCKY_XMPP_NS_STREAM, uri))
+ if (wocky_strdiff (klass->stream_element_name, localname)
+ || wocky_strdiff (klass->stream_element_ns, uri))
{
- priv->error = g_error_new_literal (WOCKY_XMPP_READER_ERROR,
+ priv->error = g_error_new (WOCKY_XMPP_READER_ERROR,
WOCKY_XMPP_READER_ERROR_INVALID_STREAM_START,
- "Invalid start of the XMPP stream");
+ "Invalid start of the XMPP stream "
+ "(expected <%s xmlns=%s>, got <%s xmlns=%s>)",
+ klass->stream_element_name, klass->stream_element_ns,
+ localname, uri);
g_queue_push_tail (priv->stanzas, NULL);
return;
}
@@ -509,8 +555,9 @@ handle_regular_element (
{
/* This can only happy in non-streaming mode when the top node
* of the document doesn't have a namespace. */
- DEBUG ("Stanza without a namespace, using dummy namespace..");
- priv->stanza = wocky_stanza_new (localname, "");
+ DEBUG ("Stanza without a namespace, using default namespace '%s'",
+ priv->default_namespace);
+ priv->stanza = wocky_stanza_new (localname, priv->default_namespace);
}
priv->node = wocky_stanza_get_top_node (priv->stanza);
diff --git a/wocky/wocky-xmpp-reader.h b/wocky/wocky-xmpp-reader.h
index fd8d63b..f8bc29f 100644
--- a/wocky/wocky-xmpp-reader.h
+++ b/wocky/wocky-xmpp-reader.h
@@ -25,7 +25,7 @@
#define __WOCKY_XMPP_READER_H__
#include <glib-object.h>
-#include "wocky-xmpp-reader-enumtypes.h"
+#include "wocky-enumtypes.h"
#include "wocky-stanza.h"
G_BEGIN_DECLS
@@ -44,6 +44,10 @@ typedef struct _WockyXmppReaderPrivate WockyXmppReaderPrivate;
struct _WockyXmppReaderClass {
/*<private>*/
GObjectClass parent_class;
+
+ /*<protected>*/
+ const gchar *stream_element_name;
+ const gchar *stream_element_ns;
};
struct _WockyXmppReader {
@@ -111,6 +115,8 @@ GType wocky_xmpp_reader_get_type (void);
WockyXmppReader * wocky_xmpp_reader_new (void);
WockyXmppReader * wocky_xmpp_reader_new_no_stream (void);
+WockyXmppReader * wocky_xmpp_reader_new_no_stream_ns (
+ const gchar *default_namespace);
WockyXmppReaderState wocky_xmpp_reader_get_state (WockyXmppReader *reader);
diff --git a/wocky/wocky-xmpp-writer.c b/wocky/wocky-xmpp-writer.c
index 3c3b110..e8acee8 100644
--- a/wocky/wocky-xmpp-writer.c
+++ b/wocky/wocky-xmpp-writer.c
@@ -23,7 +23,7 @@
* @title: WockyXmppWriter
* @short_description: Xmpp stanza to XML serializer
*
- * The #WockyXmppWriter serializes #WockyStanzas and xmpp stream opening
+ * The #WockyXmppWriter serializes #WockyStanza<!-- -->s and XMPP stream opening
* and closing to raw XML. The various functions provide a pointer to an
* internal buffer, which remains valid until the next call to the writer.
*/
diff --git a/wocky/wocky.h b/wocky/wocky.h
index 8606f7e..f3449d6 100644
--- a/wocky/wocky.h
+++ b/wocky/wocky.h
@@ -36,9 +36,20 @@
#include "wocky-data-form.h"
#include "wocky-debug.h"
#include "wocky-disco-identity.h"
+#include "wocky-enumtypes.h"
#include "wocky-jabber-auth-digest.h"
#include "wocky-jabber-auth.h"
#include "wocky-jabber-auth-password.h"
+#include "wocky-jingle-content.h"
+#include "wocky-jingle-factory.h"
+#include "wocky-jingle-info.h"
+#include "wocky-jingle-media-rtp.h"
+#include "wocky-jingle-session.h"
+#include "wocky-jingle-transport-google.h"
+#include "wocky-jingle-transport-iceudp.h"
+#include "wocky-jingle-transport-iface.h"
+#include "wocky-jingle-transport-rawudp.h"
+#include "wocky-jingle-types.h"
#include "wocky-ll-connection-factory.h"
#include "wocky-ll-connector.h"
#include "wocky-ll-contact.h"
diff --git a/wocky/wocky.pc.in b/wocky/wocky.pc.in
new file mode 100644
index 0000000..874ecf6
--- /dev/null
+++ b/wocky/wocky.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Wocky
+Description: XMPP library
+Version: @VERSION@
+Requires.private: glib-2.0 >= 2.16, gobject-2.0 >= 2.16, gio-2.0 >= 2.26
+Libs: -L${libdir} -lwocky
+Cflags: -I@HEADER_DIR@