diff options
99 files changed, 13110 insertions, 597 deletions
@@ -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, ¶m)) + { + 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@server/nick) + * @jid: the JID of the owner (owner@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@bar.org"</code> will match + * <code>"foo@bar.org"</code>, + * <code>"foo@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 <pubsub/> node of the stanza * @action_name: the action node to add to <pubsub/> * @pubsub_node: address at which to store a pointer to the <pubsub/> 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: + * + * |[ * <message from='alice@<!-- -->collabora.co.uk' to='bob@<!-- -->collabora.co.uk'> * <html xmlns='http://www.w3.org/1999/xhtml'> * <body textcolor='red'> @@ -323,14 +338,13 @@ wocky_stanza_new_with_sub_type (WockyStanzaType type, * </body> * </html> * </message> - * *<!-- -->/ - * </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 '@' * 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@ |