diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 86951a9b..314a422f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,20 +13,24 @@ jobs: run: |- sudo apt-get update && sudo apt-get install --no-install-recommends -qq -y build-essential \ ccache meson libglib2.0-dev glib-networking telepathy-gabble libsasl2-dev libxml2-dev \ - libsoup2.4-dev libsasl2-modules-gssapi-mit gnutls-bin libsqlite3-dev libssl-dev libgnutls28-dev + libsoup2.4-dev libsasl2-modules-gssapi-mit libsqlite3-dev libssl-dev libgnutls28-dev \ + gnutls-bin gobject-introspection libgirepository1.0-dev gtk-doc-tools - name: Bootstrap - run: meson _b + run: meson _b -Dgoogle-relay=true -Dintrospection=enabled -Dgtk_doc=true -Dnonstandard-scram=true - name: Syntax run: ninja -C _b check - name: Build run: ninja -C _b - name: Run tests run: meson test -C _b + - name: Build docs + run: ninja -C _b wocky-doc - name: Artifacts uses: actions/upload-artifact@v2 with: - name: Reports - path: _b/meson-logs/ + path: | + _b/meson-logs/ + _b/docs/ build-autotools: runs-on: ubuntu-latest diff --git a/docs/reference/meson.build b/docs/reference/meson.build new file mode 100644 index 00000000..5e3db366 --- /dev/null +++ b/docs/reference/meson.build @@ -0,0 +1,13 @@ + +gnome.gtkdoc(meson.project_name(), + main_sgml: 'wocky-docs.sgml', + dependencies: wocky_dep, + src_dir: [ + meson.current_build_dir()/'..'/'..'/'wocky', + meson.current_source_dir()/'..'/'..'/'wocky', + ], + namespace: 'wocky', + mkdb_args: [ '--sgml-mode', '--output-format=xml'], + scan_args: ['--rebuild-types', '--rebuild-sections'], + install: true +) diff --git a/docs/reference/wocky-docs.sgml b/docs/reference/wocky-docs.sgml index 69daa83b..9aaaaacc 100644 --- a/docs/reference/wocky-docs.sgml +++ b/docs/reference/wocky-docs.sgml @@ -1,10 +1,10 @@ ]> - + Wocky Reference Manual @@ -18,12 +18,15 @@ + + + + - @@ -61,6 +64,7 @@ + @@ -72,6 +76,8 @@ + + @@ -83,6 +89,18 @@ API Index + + Index of new symbols in 0.19.0 + + + + Index of new symbols in 0.7.27 + + + + Index of new symbols in 0.5.14 + + diff --git a/meson.build b/meson.build index 424fc896..385ea144 100644 --- a/meson.build +++ b/meson.build @@ -109,6 +109,10 @@ if get_option('debug') defines += 'ENABLE_DEBUG' endif +if get_option('nonstandard-scram') + defines += 'WOCKY_SCRAM_NONSTANDARD' +endif + if get_option('google-relay') defines += 'ENABLE_GOOGLE_RELAY' endif @@ -170,3 +174,7 @@ if get_option('code-style-check') shell, meson.source_root()/'tools'/'check-c-style.sh', wocky_check_files ]) endif + +if get_option('gtk_doc') + subdir('docs/reference') +endif diff --git a/meson_options.txt b/meson_options.txt index 1d4e687e..b33eb24c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,6 +1,7 @@ -option('gtk_doc', type: 'boolean', value: false, description: 'Enable Gtk-Doc generation') -option('introspection', type: 'feature', value: 'auto', description: 'Build Introspection data') -option('code-style-check', type: 'boolean', description: 'enable coding style checks') +option('gtk_doc', type: 'boolean', value: false, description: 'Enable Gtk-Doc generation', yield: true) +option('introspection', type: 'feature', value: 'auto', description: 'Build Introspection data', yield: true) +option('code-style-check', type: 'boolean', description: 'enable coding style checks', yield: true) option('install-headers', type: 'string', description: 'install development headers here') option('libdir-suffix', type: 'string', description: 'install a shared library into project-specific subdir') -option('google-relay', type: 'boolean', value: false, description: 'enable google jingle relay support') +option('google-relay', type: 'boolean', value: false, description: 'enable google jingle relay support', yield: true) +option('nonstandard-scram', type: 'boolean', value: false, description: 'enable non-standard scram methods', yield: true) diff --git a/tests/Makefile.am b/tests/Makefile.am index a3cc7177..2967acb4 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -88,6 +88,7 @@ TEST_PROGS = \ wocky-session-test \ wocky-stanza-test \ wocky-tls-test \ + wocky-sm-test \ wocky-utils-test \ wocky-xmpp-connection-test \ wocky-xmpp-node-test \ @@ -229,6 +230,19 @@ wocky_tls_test_SOURCES = \ wocky-test-stream.c wocky-test-stream.h wocky_tls_test_CFLAGS = $(AM_CFLAGS) $(TLSDEFS) +EXTRA_wocky_sm_test_DEPENDENCIES = $(CA_DIR) certs +wocky_sm_test_SOURCES = \ + wocky-sm-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.c test-resolver.h +wocky_sm_test_LDADD = $(LDADD) @LIBSASL2_LIBS@ +wocky_sm_test_CFLAGS = $(AM_CFLAGS) @LIBSASL2_CFLAGS@ $(TLSDEFS) + wocky_utils_test_SOURCES = wocky-utils-test.c wocky_xmpp_connection_test_SOURCES = \ @@ -297,7 +311,7 @@ test-%: wocky-%-test .PHONY: test test-report include $(top_srcdir)/tools/check-coding-style.mk -check-local: test check-coding-style +check-local: check-coding-style test ############################################################################ diff --git a/tests/meson.build b/tests/meson.build index 602f29d4..725dfad1 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -144,6 +144,16 @@ tests = { 'wocky-test-stream.c', 'wocky-test-stream.h', 'wocky-tls-test.c', ], + 'wocky-sm-test': [ + '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.c', 'test-resolver.h', + 'wocky-sm-test.c', + ], 'wocky-utils-test': [ 'wocky-utils-test.c', ], diff --git a/tests/wocky-connector-test.c b/tests/wocky-connector-test.c index 871997ff..e80efd75 100644 --- a/tests/wocky-connector-test.c +++ b/tests/wocky-connector-test.c @@ -74,6 +74,12 @@ #define DEFAULT_SASL_MECH "SCRAM-SHA-1" #endif +#ifdef WOCKY_SCRAM_NONSTANDARD +#define BINDING_SCRAM_MECH "SCRAM-SHA-512-PLUS" +#else +#define BINDING_SCRAM_MECH "SCRAM-SHA-256-PLUS" +#endif + #define PORT_XMPP 5222 #define PORT_NONE 0 @@ -944,7 +950,7 @@ test_t tests[] = { "/connector/auth/sasl/binding", NOISY, { S_NO_ERROR }, - { { TLS, "SCRAM-SHA-512-PLUS" }, + { { TLS, BINDING_SCRAM_MECH }, { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK }, { "moose", "something" }, PORT_XMPP }, @@ -956,7 +962,7 @@ test_t tests[] = { "/connector/auth/sasl/bad-binding-data", NOISY, { S_WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, -1 }, - { { TLS, "SCRAM-SHA-512-PLUS" }, + { { TLS, BINDING_SCRAM_MECH }, { SERVER_PROBLEM_MANGLED_BINDING_DATA, CONNECTOR_OK }, { "moose", "something" }, PORT_XMPP }, @@ -968,7 +974,7 @@ test_t tests[] = { "/connector/auth/sasl/bad-binding-flag", NOISY, { S_WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, -1 }, - { { TLS, "SCRAM-SHA-512-PLUS" }, + { { TLS, BINDING_SCRAM_MECH }, { SERVER_PROBLEM_MANGLED_BINDING_FLAG, CONNECTOR_OK }, { "moose", "something" }, PORT_XMPP }, @@ -980,7 +986,7 @@ test_t tests[] = { "/connector/auth/sasl/scrambled-binding", NOISY, { S_WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_INVALID_REPLY, -1 }, - { { TLS, "SCRAM-SHA-512-PLUS" }, + { { TLS, BINDING_SCRAM_MECH }, { SERVER_PROBLEM_SCRAMBLED_BINDING, CONNECTOR_OK }, { "moose", "something" }, PORT_XMPP }, diff --git a/tests/wocky-sm-test.c b/tests/wocky-sm-test.c new file mode 100644 index 00000000..26dc8aab --- /dev/null +++ b/tests/wocky-sm-test.c @@ -0,0 +1,926 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef G_OS_WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#ifdef G_OS_UNIX +#include +#endif + +#include + +#include "wocky-test-connector-server.h" +#include "test-resolver.h" +#include "wocky-test-helper.h" +#include "config.h" + +#ifdef G_LOG_DOMAIN +#undef G_LOG_DOMAIN +#endif +#define G_LOG_DOMAIN "wocky-sm-test" + +#define SASL_DB_NAME "sasl-test.db" + +#define INVISIBLE_HOST "unreachable.host" +#define VISIBLE_HOST "reachable.host" +#define REACHABLE "127.0.0.1" +#define UNREACHABLE "127.255.255.255" +#define DUFF_H0ST "no_such_host.at.all" + +#define OLD_SSL TRUE +#define OLD_JABBER TRUE +#define XMPP_V1 FALSE +#define STARTTLS FALSE + +#define CERT_CHECK_STRICT FALSE +#define CERT_CHECK_LENIENT TRUE + +#define TLS_REQUIRED TRUE +#define PLAINTEXT_OK FALSE + +#define QUIET TRUE +#define NOISY FALSE + +#define TLS TRUE +#define NOTLS FALSE + +#define PLAIN FALSE +#define DIGEST TRUE + +#define DEFAULT_SASL_MECH "SCRAM-SHA-256" + +#define PORT_XMPP 5222 +#define PORT_NONE 0 + +#define OK 0 +#define CONNECTOR_OK { OK, OK, OK, OK, OK, OK, OK } +#define SM_PROBLEM(x) { OK, OK, OK, OK, OK, OK, SM_PROBLEM_##x } + + +static GError *error = NULL; +static GResolver *original; +static GResolver *kludged; +static GMainLoop *mainloop; + +enum { + S_NO_ERROR = 0, + S_WOCKY_AUTH_ERROR, + S_WOCKY_CONNECTOR_ERROR, + S_WOCKY_XMPP_CONNECTION_ERROR, + S_WOCKY_TLS_CERT_ERROR, + S_WOCKY_XMPP_STREAM_ERROR, + S_G_IO_ERROR, + S_G_RESOLVER_ERROR, + S_ANY_ERROR = 0xff +}; + +#define MAP(x) case S_##x: return x +static GQuark +map_static_domain (gint domain) +{ + switch (domain) + { + MAP (WOCKY_AUTH_ERROR); + MAP (WOCKY_CONNECTOR_ERROR); + MAP (WOCKY_XMPP_CONNECTION_ERROR); + MAP (WOCKY_TLS_CERT_ERROR); + MAP (WOCKY_XMPP_STREAM_ERROR); + MAP (G_IO_ERROR); + MAP (G_RESOLVER_ERROR); + default: + g_assert_not_reached (); + } +} +#undef MAP + + +typedef void (*test_setup) (gpointer); + +typedef struct _ServerParameters ServerParameters; +struct _ServerParameters { + struct { gboolean tls; gchar *auth_mech; gchar *version; } features; + struct { ServerProblem sasl; ConnectorProblem conn; } problem; + struct { gchar *user; gchar *pass; } auth; + guint port; + CertSet cert; + + /* Extra server for see-other-host problem */ + ServerParameters *extra_server; + + /* Runtime */ + TestConnectorServer *server; + GIOChannel *channel; + guint watch; +}; + +typedef struct { + gchar *desc; + gboolean quiet; + struct { int domain; + int code; + int fallback_code; + gchar *mech; + gchar *used_mech; + gpointer xmpp; + gchar *jid; + gchar *sid; + } result; + ServerParameters server_parameters; + struct { char *srv; guint port; char *host; char *addr; char *srvhost; } dns; + struct { + gboolean require_tls; + struct { gchar *jid; gchar *pass; gboolean secure; gboolean tls; } auth; + struct { gchar *host; guint port; gboolean jabber; gboolean ssl; gboolean lax_ssl; const gchar *ca; } options; + int op; + test_setup setup; + } client; + + /* Runtime */ + WockyConnector *connector; + WockySession *session; + WockyPorter *porter; + gboolean ok; +} test_t; + +test_t tests[] = + { /* basic connection test, no SRV record, no host or port supplied: */ + /* + { "/name/of/test", + SUPPRESS_STDERR, + // result of test: + { DOMAIN, CODE, FALLBACK_CODE, + AUTH_MECH_USED, XMPP_CONNECTION_PLACEHOLDER }, + // When an error is expected it should match the domain and either + // the given CODE or the FALLBACK_CODE (GIO over time became more + // specific about the error codes it gave in certain conditions) + + // Server Details: + { { TLS_SUPPORT, AUTH_MECH_OR_NULL_FOR_ALL }, + { SERVER_PROBLEM..., CONNECTOR_PROBLEM... }, + { USERNAME, PASSWORD }, + SERVER_LISTEN_PORT, SERVER_CERT }, + + // Fake DNS Record: + // SRV_HOSTs SRV record → { HOSTNAME, PORT } + // HOSTs A record → IP_ADDRESS + // SRV_HOSTs A record → IP_ADDR_OF_SRV_HOST + { SRV_HOST, PORT, HOSTNAME, IP_ADDRESS, IP_ADDR_OF_SRV_HOST }, + + // Client Details + { TLS_REQUIRED, + { BARE_JID, PASSWORD, MUST_BE_DIGEST_AUTH, MUST_BE_SECURE }, + { XMPP_HOSTNAME_OR_NULL, XMPP_PORT_OR_ZERO, OLD_JABBER, OLD_SSL } } + SERVER_PROCESS_ID }, */ + + /* simple connection, followed by checks on all the internal state * + * and get/set property methods to make sure they work */ + + /* ********************************************************************* */ + /* SM test conditions */ + { "/tls/sasl/sm/enable", + NOISY, + { S_NO_ERROR }, + { { TLS, DEFAULT_SASL_MECH }, + { SERVER_PROBLEM_NO_PROBLEM, SM_PROBLEM (ENABLED) }, + { "moose", "something" }, + PORT_XMPP }, + { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE }, + { TLS_REQUIRED, + { "moose@weasel-juice.org", "something", DIGEST, TLS }, + { NULL, 0 } } }, + + { "/tls/sasl/sm/ack0", + NOISY, + { S_NO_ERROR }, + { { TLS, DEFAULT_SASL_MECH }, + { SERVER_PROBLEM_NO_PROBLEM, SM_PROBLEM (ACK0) }, + { "moose", "something" }, + PORT_XMPP }, + { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE }, + { TLS_REQUIRED, + { "moose@weasel-juice.org", "something", DIGEST, TLS }, + { NULL, 0 } } }, + + { "/tls/sasl/sm/ack1", + NOISY, + { S_NO_ERROR }, + { { TLS, DEFAULT_SASL_MECH }, + { SERVER_PROBLEM_NO_PROBLEM, SM_PROBLEM (ACK1) }, + { "moose", "something" }, + PORT_XMPP }, + { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE }, + { TLS_REQUIRED, + { "moose@weasel-juice.org", "something", DIGEST, TLS }, + { NULL, 0 } } }, + + { "/tls/sasl/sm/ack-overrun", + NOISY, + { S_WOCKY_XMPP_STREAM_ERROR, WOCKY_XMPP_STREAM_ERROR_UNDEFINED_CONDITION, }, + { { TLS, DEFAULT_SASL_MECH }, + { SERVER_PROBLEM_NO_PROBLEM, SM_PROBLEM (ACK0_OVER) }, + { "moose", "something" }, + PORT_XMPP }, + { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE }, + { TLS_REQUIRED, + { "moose@weasel-juice.org", "something", DIGEST, TLS }, + { NULL, 0 } } }, + + { "/tls/sasl/sm/ack-wrap0", + NOISY, + { S_NO_ERROR }, + { { TLS, DEFAULT_SASL_MECH }, + { SERVER_PROBLEM_NO_PROBLEM, SM_PROBLEM (WRAP0) }, + { "moose", "something" }, + PORT_XMPP }, + { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE }, + { TLS_REQUIRED, + { "moose@weasel-juice.org", "something", DIGEST, TLS }, + { NULL, 0 } } }, + + { "/tls/sasl/sm/ack-wrap1", + NOISY, + { S_NO_ERROR }, + { { TLS, DEFAULT_SASL_MECH }, + { SERVER_PROBLEM_NO_PROBLEM, SM_PROBLEM (WRAP1) }, + { "moose", "something" }, + PORT_XMPP }, + { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE }, + { TLS_REQUIRED, + { "moose@weasel-juice.org", "something", DIGEST, TLS }, + { NULL, 0 } } }, + + { "/tls/sasl/sm/wrap-overrun", + NOISY, + { S_WOCKY_XMPP_STREAM_ERROR, WOCKY_XMPP_STREAM_ERROR_UNDEFINED_CONDITION, }, + { { TLS, DEFAULT_SASL_MECH }, + { SERVER_PROBLEM_NO_PROBLEM, SM_PROBLEM (WRAP0_OVER) }, + { "moose", "something" }, + PORT_XMPP }, + { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE }, + { TLS_REQUIRED, + { "moose@weasel-juice.org", "something", DIGEST, TLS }, + { NULL, 0 } } }, + + /* we are done, cap the list: */ + { NULL } + }; + +/* ************************************************************************* */ +#define STRING_OK(x) (((x) != NULL) && (*x != '\0')) + +static void +setup_dummy_dns_entries (const test_t *test) +{ + TestResolver *tr = TEST_RESOLVER (kludged); + guint port = test->dns.port ? test->dns.port : test->server_parameters.port; + const char *domain = test->dns.srv; + const char *host = test->dns.host; + const char *addr = test->dns.addr; + const char *s_ip = test->dns.srvhost; + + test_resolver_reset (tr); + + if (STRING_OK (domain) && STRING_OK (host)) + test_resolver_add_SRV (tr, "xmpp-client", "tcp", domain, host, port); + + if (STRING_OK (domain) && STRING_OK (s_ip)) + test_resolver_add_A (tr, domain, s_ip); + + if (STRING_OK (host) && STRING_OK (addr)) + test_resolver_add_A (tr, host, addr); + + test_resolver_add_A (tr, INVISIBLE_HOST, UNREACHABLE); + test_resolver_add_A (tr, VISIBLE_HOST, REACHABLE); +} + +/* ************************************************************************* */ +/* Dummy XMPP server */ +static void start_dummy_xmpp_server (ServerParameters *srv); + +static gboolean +client_connected (GIOChannel *channel, + GIOCondition cond, + gpointer data) +{ + ServerParameters *srv = data; + struct sockaddr_in client; + socklen_t clen = sizeof (client); + int ssock = g_io_channel_unix_get_fd (channel); + int csock = accept (ssock, (struct sockaddr *) &client, &clen); + GSocket *gsock = g_socket_new_from_fd (csock, NULL); + ConnectorProblem *cproblem = &srv->problem.conn; + + GSocketConnection *gconn; + + if (csock < 0) + { + perror ("accept() failed"); + g_warning ("accept() failed on socket that should have been ready."); + return TRUE; + } + + if (!srv->features.tls) + cproblem->xmpp |= XMPP_PROBLEM_NO_TLS; + + gconn = g_object_new (G_TYPE_SOCKET_CONNECTION, "socket", gsock, NULL); + g_object_unref (gsock); + + srv->server = test_connector_server_new (G_IO_STREAM (gconn), + srv->features.auth_mech, + srv->auth.user, + srv->auth.pass, + srv->features.version, + NULL, + cproblem, + srv->problem.sasl, + srv->cert); + g_object_unref (gconn); + + /* Recursively start extra servers */ + if (srv->extra_server != NULL) + { + test_connector_server_set_other_host (srv->server, REACHABLE, + srv->extra_server->port); + start_dummy_xmpp_server (srv->extra_server); + } + + test_connector_server_start (srv->server); + + srv->watch = 0; + return FALSE; +} + +static void +start_dummy_xmpp_server (ServerParameters *srv) +{ + int ssock; + int reuse = 1; + struct sockaddr_in server; + int res = -1; + guint port = srv->port; + + if (port == 0) + return; + + memset (&server, 0, sizeof (server)); + + server.sin_family = AF_INET; + + /* mingw doesn't support aton or pton so using more portable inet_addr */ + server.sin_addr.s_addr = inet_addr ((const char * ) REACHABLE); + 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)); + + if (res != 0) + { + int code = errno; + char *err = g_strdup_printf ("bind to " REACHABLE ":%d failed", port); + perror (err); + g_free (err); + exit (code); + } + + res = listen (ssock, 1024); + if (res != 0) + { + int code = errno; + char *err = g_strdup_printf ("listen on " REACHABLE ":%d failed", port); + perror (err); + g_free (err); + exit (code); + } + + srv->channel = g_io_channel_unix_new (ssock); + g_io_channel_set_flags (srv->channel, G_IO_FLAG_NONBLOCK, NULL); + srv->watch = g_io_add_watch (srv->channel, G_IO_IN|G_IO_PRI, + client_connected, srv); + g_io_channel_set_close_on_unref (srv->channel, TRUE); + return; +} +/* ************************************************************************* */ +static void +test_server_teardown_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GMainLoop *loop = user_data; + + g_assert (test_connector_server_teardown_finish ( + TEST_CONNECTOR_SERVER (source), result, NULL)); + + g_main_loop_quit (loop); +} + +static gboolean +test_server_idle_quit_loop_cb (GMainLoop *loop) +{ + static int retries = 0; + + if (retries == 5) + { + g_main_loop_quit (loop); + retries = 0; + return G_SOURCE_REMOVE; + } + else + { + retries ++; + return G_SOURCE_CONTINUE; + } +} + +static void +test_server_teardown (test_t *test, + ServerParameters *srv) +{ + /* Recursively teardown extra servers */ + if (srv->extra_server != NULL) + test_server_teardown (test, srv->extra_server); + srv->extra_server = NULL; + + if (srv->server != NULL) + { + GMainLoop *loop = g_main_loop_new (NULL, FALSE); + + if (test->result.used_mech == NULL) + { + test->result.used_mech = g_strdup ( + test_connector_server_get_used_mech (srv->server)); + } + + /* let the server dispatch any pending events before + * forcing it to tear down */ + g_idle_add ((GSourceFunc) test_server_idle_quit_loop_cb, loop); + g_main_loop_run (loop); + + /* Run until server is down */ + test_connector_server_teardown (srv->server, + test_server_teardown_cb, loop); + g_main_loop_run (loop); + + g_clear_object (&srv->server); + g_main_loop_unref (loop); + } + + if (srv->watch != 0) + g_source_remove (srv->watch); + srv->watch = 0; + + if (srv->channel != NULL) + g_io_channel_unref (srv->channel); + srv->channel = NULL; +} + +static void +test_done (GObject *source, + GAsyncResult *res, + gpointer data) +{ + test_t *test = data; + + if (WOCKY_IS_XMPP_CONNECTION (source)) + test->result.xmpp = g_object_ref (source); + else if (WOCKY_IS_C2S_PORTER (source)) + { + WockyXmppConnection *conn = NULL; + g_object_get (source, + "connection", &conn, + NULL); + wocky_porter_close_finish (WOCKY_PORTER (source), res, &error); + g_object_unref (source); + test->result.xmpp = g_object_ref (conn); + } + + test_server_teardown (test, &test->server_parameters); + + g_main_loop_quit (mainloop); +} + +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 void +iq_result (GObject *source, + GAsyncResult *res, + gpointer data); + +static gboolean +test_phase (gpointer data) +{ + test_t *test = data; + SMProblem sm = test->server_parameters.problem.conn.sm; + const WockyPorterSmCtx *smc; + + if (test->porter == NULL) + { + test_done (NULL, NULL, test); + return FALSE; + } + + smc = wocky_c2s_porter_get_sm_ctx (WOCKY_C2S_PORTER (test->porter)); + + g_debug ("Reqs: %d", smc->reqs); + g_assert_cmpint (smc->reqs, ==, 0); + if (sm & SM_PROBLEM_WRAP) + { + if (test->client.op == 0) + g_assert_cmpint (smc->snt_acked, ==, G_MAXUINT32); + else + g_assert_cmpint (smc->snt_acked, ==, 0); + } + else + g_assert_cmpint (smc->snt_acked, ==, test->client.op); + + if (sm & SM_PROBLEM_ACK1 && test->client.op == 0) + { + WockyStanza *iq = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, + WOCKY_STANZA_SUB_TYPE_SET, + NULL, NULL, + '(', "session", ':', WOCKY_XMPP_NS_SESSION, + ')', + NULL); + wocky_porter_send_iq_async (WOCKY_PORTER (test->porter), iq, NULL, + iq_result, test); + test->client.op++; + g_object_unref (iq); + return FALSE; + } + + wocky_porter_close_async (WOCKY_PORTER (test->porter), NULL, test_done, + test); + return FALSE; +} + +static void +smreqd (GObject *source, + GAsyncResult *res, + gpointer data) +{ + test_t *test = data; + WockyC2SPorter *p = WOCKY_C2S_PORTER (source); + const WockyPorterSmCtx *smc = wocky_c2s_porter_get_sm_ctx (p); + + g_assert_true ( + wocky_c2s_porter_send_whitespace_ping_finish (p, res, &error)); + g_assert_cmpint (smc->reqs, ==, 1); + + /* and wait for `a` in response */ + + g_idle_add (test_phase, test); +} + +static void +iq_result (GObject *source, + GAsyncResult *res, + gpointer data) +{ + test_t *test = data; + WockyPorter *p = WOCKY_PORTER (source); + WockyC2SPorter *cp = WOCKY_C2S_PORTER (source); + const WockyPorterSmCtx *smc = wocky_c2s_porter_get_sm_ctx (cp); + WockyStanza *iq = wocky_porter_send_iq_finish (p, res, &error); + + g_assert_nonnull (iq); + g_object_unref (iq); + + g_assert_cmpint (smc->snt_count, ==, 1); + g_assert_cmpint (smc->rcv_count, ==, 1); + + g_debug ("Acked: %zu", smc->snt_acked); + wocky_c2s_porter_send_whitespace_ping_async (WOCKY_C2S_PORTER (test->porter), + NULL, smreqd, test); +} + +static void +remote_error_cb (WockyPorter *porter, + GQuark domain, + gint code, + gchar *msg, + gpointer data) +{ + test_t *test = data; + + g_debug ("Error: %s", msg); + g_set_error_literal (&error, domain, code, msg); + + if (test->server_parameters.problem.conn.sm & SM_PROBLEM_OVER) + { + test->porter = NULL; + g_object_unref (porter); + } +} + +static void +enabled (GObject *source, + GAsyncResult *result, + gpointer data) +{ + test_t *test = data; + WockyXmppConnection *conn = WOCKY_XMPP_CONNECTION (source); + SMProblem sm = test->server_parameters.problem.conn.sm; + const WockyStanza *enabled; + WockyNode *enn; + WockyPorterSmCtx *smc; + + enabled = wocky_xmpp_connection_peek_stanza_finish (conn, result, &error); + g_assert_nonnull (enabled); + g_assert_null (error); + + enn = wocky_stanza_get_top_node ((WockyStanza *) enabled); + g_assert_cmpstr (enn->name, ==, "enabled"); + + test->session = wocky_session_new_with_connection (conn, test->result.jid); + test->porter = wocky_session_get_porter (test->session); + if (sm & SM_PROBLEM_OVER) + g_signal_connect (test->porter, "remote-error", + G_CALLBACK (remote_error_cb), test); + wocky_porter_start (test->porter); + + /* Do NOT EVER do that in real life - context must not me modified */ + smc = (WockyPorterSmCtx *) wocky_c2s_porter_get_sm_ctx ( + WOCKY_C2S_PORTER (test->porter)); + g_assert_nonnull (smc); + g_assert_true (smc->enabled); + + /* let the world spin - consume the enabled */ + g_main_context_iteration (NULL, FALSE); + + g_assert_true (smc->resumable); + g_assert_cmpstr (smc->id, ==, "deadbeef"); + + if (sm < SM_PROBLEM_ACK0) + { + test_done (G_OBJECT (conn), NULL, test); + return; + } + + /* let the world spin again - there will be `r` from ... */ + g_main_context_iteration (NULL, FALSE); + /* ... and `a` to the server */ + g_main_context_iteration (NULL, FALSE); + + /* before we do get the chance to process the `a`nswer let set some tests */ + if (sm & SM_PROBLEM_WRAP) + { + g_debug ("unwrapping snt_acked"); + smc->snt_acked = G_MAXUINT32-1; + } + + /* now send our r there */ + wocky_c2s_porter_send_whitespace_ping_async (WOCKY_C2S_PORTER (test->porter), + NULL, smreqd, test); +} + +static void +connected (GObject *source, + GAsyncResult *res, + gpointer data) +{ + test_t *test = data; + WockyConnector *wcon = WOCKY_CONNECTOR (source); + WockyXmppConnection *conn = NULL; + + error = NULL; + conn = wocky_connector_connect_finish (wcon, res, + &test->result.jid, &test->result.sid, &error); + g_assert_nonnull (conn); + g_assert_null (error); + + g_debug ("Connected %d", test->server_parameters.problem.conn.sm); + wocky_xmpp_connection_peek_stanza_async (conn, NULL, enabled, test); +} + +static gboolean +start_test (gpointer data) +{ + test_t *test = data; + + if (test->client.setup != NULL) + (test->client.setup) (test); + + g_debug ("Start %d", test->server_parameters.problem.conn.sm); + wocky_connector_connect_async (test->connector, NULL, connected, data); + return FALSE; +} + +static void +run_test (gpointer data) +{ + WockyConnector *wcon = NULL; + WockyTLSHandler *handler; + test_t *test = data; + struct stat dummy; + gchar *base; + char *path; + const gchar *ca; + + /* clean up any leftover messes from previous tests */ + /* unlink the sasl db tmpfile, it will cause a deadlock */ + base = g_get_current_dir (); + path = g_strdup_printf ("%s/__db.%s", base, SASL_DB_NAME); + g_free (base); + g_assert ((g_stat (path, &dummy) != 0) || (g_unlink (path) == 0)); + g_free (path); + /* end of cleanup block */ + + start_dummy_xmpp_server (&test->server_parameters); + setup_dummy_dns_entries (test); + + ca = test->client.options.ca ? test->client.options.ca : TLS_CA_CRT_FILE; + + if (test->client.options.host && test->client.options.port == 0) + test->client.options.port = test->server_parameters.port; + + /* insecure tls cert/etc not yet implemented */ + handler = wocky_tls_handler_new (test->client.options.lax_ssl); + + wcon = g_object_new ( WOCKY_TYPE_CONNECTOR, + "jid" , test->client.auth.jid, + "password" , test->client.auth.pass, + "xmpp-server" , test->client.options.host, + "xmpp-port" , test->client.options.port, + "tls-required" , test->client.require_tls, + "encrypted-plain-auth-ok" , !test->client.auth.secure, + /* this refers to PLAINTEXT vs CRYPT, not PLAIN vs DIGEST */ + "plaintext-auth-allowed" , !test->client.auth.tls, + "legacy" , test->client.options.jabber, + "old-ssl" , test->client.options.ssl, + "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) + { + if (error != NULL) + fprintf (stderr, "Error: %s.%d: %s\n", + g_quark_to_string (error->domain), + error->code, + error->message); + g_assert_no_error (error); + + if (test->client.op >= 0) + { + g_assert (test->result.xmpp != NULL); + + /* make sure we selected the right auth mechanism */ + if (test->result.mech != NULL) + { + g_assert_cmpstr (test->result.mech, ==, + test->result.used_mech); + } + + /* we got a JID back, I hope */ + g_assert (test->result.jid != NULL); + g_assert (*test->result.jid != '\0'); + g_free (test->result.jid); + + /* we got a SID back, I hope */ + g_assert (test->result.sid != NULL); + g_assert (*test->result.sid != '\0'); + g_free (test->result.sid); + } + } + else + { + g_assert (test->result.xmpp == NULL); + + if (test->result.domain != S_ANY_ERROR) + { + /* We want the error to match either of result.code or + * result.fallback_code, but don't care which. + * The expected error domain is the same for either code. + */ + if (error->code == test->result.fallback_code) + g_assert_error (error, map_static_domain (test->result.domain), + test->result.fallback_code); + else + g_assert_error (error, map_static_domain (test->result.domain), + test->result.code); + } + } + + if (wcon != NULL) + g_object_unref (wcon); + + if (error != NULL) + g_error_free (error); + + if (test->result.xmpp != NULL) + g_object_unref (test->result.xmpp); + + g_free (test->result.used_mech); + error = NULL; +} + +int +main (int argc, + char **argv) +{ + int i; + gchar *base; + gchar *path = NULL; + struct stat dummy; + int result; + + test_init (argc, argv); + + /* hook up the fake DNS resolver that lets us divert A and SRV queries * + * into our local cache before asking the real DNS */ + original = g_resolver_get_default (); + kludged = g_object_new (TEST_TYPE_RESOLVER, "real-resolver", original, NULL); + g_resolver_set_default (kludged); + + /* unlink the sasl db, we want to test against a fresh one */ + base = g_get_current_dir (); + path = g_strdup_printf ("%s/%s", base, SASL_DB_NAME); + g_free (base); + g_assert ((g_stat (path, &dummy) != 0) || (g_unlink (path) == 0)); + g_free (path); + + mainloop = g_main_loop_new (NULL, FALSE); + +#ifdef HAVE_LIBSASL2 + + for (i = 0; tests[i].desc != NULL; i++) + g_test_add_data_func (tests[i].desc, &tests[i], (test_func)run_test); + +#else + + g_message ("libsasl2 not found: skipping SCRAM SASL tests"); + for (i = 0; tests[i].desc != NULL; i++) + { + if (!wocky_strdiff (tests[i].result.mech, DEFAULT_SASL_MECH)) + continue; + g_test_add_data_func (tests[i].desc, &tests[i], (test_func)run_test); + } + +#endif + + 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 9d46810f..adc29443 100644 --- a/tests/wocky-test-connector-server.c +++ b/tests/wocky-test-connector-server.c @@ -108,6 +108,7 @@ struct _TestConnectorServerPrivate GCancellable *cancellable; gint outstanding; + gint rcv_count;; GTask *teardown_task; struct { ServerProblem sasl; ConnectorProblem *connector; } problem; @@ -179,6 +180,7 @@ test_connector_server_init (TestConnectorServer *self) priv->tls_started = FALSE; priv->authed = FALSE; priv->cancellable = g_cancellable_new (); + priv->rcv_count = -1; } static void @@ -199,6 +201,14 @@ static void handle_auth (TestConnectorServer *self, WockyStanza *xml); static void handle_starttls (TestConnectorServer *self, WockyStanza *xml); +static void handle_enable (TestConnectorServer *self, + WockyStanza *xml); +static void handle_a (TestConnectorServer *self, + WockyStanza *xml); +static void handle_r (TestConnectorServer *self, + WockyStanza *xml); +static void handle_error (TestConnectorServer *self, + WockyStanza *xml); static void after_auth (GObject *source, @@ -237,6 +247,10 @@ static stanza_handler handlers[] = { HANDLER (SASL_AUTH, auth), HANDLER (TLS, starttls), + HANDLER (SM3, enable), + HANDLER (SM3, a), + HANDLER (SM3, r), + HANDLER (STREAM, error), { NULL, NULL, NULL } }; @@ -881,6 +895,8 @@ iq_set_session_XMPP_SESSION (TestConnectorServer *self, iq = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_RESULT, NULL, NULL, + '@', "id", wocky_node_get_attribute (wocky_stanza_get_top_node (xml), + "id"), '(', "session", ':', WOCKY_XMPP_NS_SESSION, ')', NULL); @@ -1032,6 +1048,189 @@ handle_starttls (TestConnectorServer *self, g_object_unref (xml); } +static void +sm_ack (GObject *source, + GAsyncResult *result, + gpointer data) +{ + WockyXmppConnection *conn = WOCKY_XMPP_CONNECTION (source); + TestConnectorServer *self = TEST_CONNECTOR_SERVER (data); + TestConnectorServerPrivate *priv = self->priv; + WockyStanza *ack; + WockyNode *top; + const gchar *h; + + DEBUG ("Validating porter R handler"); + + ack = wocky_xmpp_connection_recv_stanza_finish (conn, result, NULL); + g_assert_nonnull (ack); + + top = wocky_stanza_get_top_node (ack); + g_assert_cmpstr (top->name, ==, "a"); + + h = wocky_node_get_attribute (top, "h"); + g_assert_nonnull (h); + g_assert_cmpstr (h, ==, "0"); + + server_enc_outstanding (self); + DEBUG ("waiting for next stanza from client"); + wocky_xmpp_connection_recv_stanza_async (priv->conn, + priv->cancellable, xmpp_handler, self); +} + +static void +sm_get_ack (GObject *source, + GAsyncResult *result, + gpointer data) +{ + WockyXmppConnection *conn = WOCKY_XMPP_CONNECTION (source); + TestConnectorServer *self = TEST_CONNECTOR_SERVER (data); + TestConnectorServerPrivate *priv = self->priv; + + DEBUG (""); + + g_assert_true ( + wocky_xmpp_connection_send_stanza_finish (conn, result, NULL)); + DEBUG ("waiting for ack from client"); + wocky_xmpp_connection_recv_stanza_async (conn, priv->cancellable, sm_ack, + data); +} + +static void +sm_req (GObject *source, + GAsyncResult *result, + gpointer data) +{ + WockyXmppConnection *conn = WOCKY_XMPP_CONNECTION (source); + TestConnectorServer *self = TEST_CONNECTOR_SERVER (data); + TestConnectorServerPrivate *priv = self->priv; + WockyStanza *reply = wocky_stanza_new ("r", WOCKY_XMPP_NS_SM3); + + DEBUG (""); + g_assert_true ( + wocky_xmpp_connection_send_stanza_finish (conn, result, NULL)); + + wocky_xmpp_connection_send_stanza_async (priv->conn, reply, NULL, sm_get_ack, + self); + g_object_unref (reply); +} + +static void +handle_enable (TestConnectorServer *self, + WockyStanza *xml) +{ + TestConnectorServerPrivate *priv = self->priv; + WockyStanza *reply = wocky_stanza_new ("enabled", WOCKY_XMPP_NS_SM3); + WockyNode *top = wocky_stanza_get_top_node (reply); + + DEBUG (""); + /* we simply and blindly respond to this */ + wocky_node_set_attribute (top, "id", "deadbeef"); + wocky_node_set_attribute (top, "resume", "true"); + priv->rcv_count = 0; + + /* if we have other SM tests - proceed with r/a */ + if (priv->problem.connector->sm > SM_PROBLEM_ENABLED) + wocky_xmpp_connection_send_stanza_async (priv->conn, reply, NULL, + sm_req, self); + else + wocky_xmpp_connection_send_stanza_async (priv->conn, reply, NULL, + finished, self); + + g_object_unref (xml); + g_object_unref (reply); +} + +static void +handle_a (TestConnectorServer *self, + WockyStanza *xml) +{ + TestConnectorServerPrivate *priv = self->priv; + WockyNode *top = wocky_stanza_get_top_node (xml); + const gchar *h = wocky_node_get_attribute (top, "h"); + + DEBUG ("%s", h); + g_assert_nonnull (h); + + g_object_unref (xml); + + server_enc_outstanding (self); + DEBUG ("waiting for next stanza from client"); + wocky_xmpp_connection_recv_stanza_async (priv->conn, + priv->cancellable, xmpp_handler, self); +} + +static void +handle_r (TestConnectorServer *self, + WockyStanza *xml) +{ + TestConnectorServerPrivate *priv = self->priv; + WockyStanza *reply = wocky_stanza_new ("a", WOCKY_XMPP_NS_SM3); + WockyNode *top = wocky_stanza_get_top_node (reply); + g_autofree gchar *h = NULL; + guint32 hn; + + g_object_unref (xml); + + if (priv->problem.connector->sm == SM_PROBLEM_WRAP0) + hn = G_MAXUINT32; + else if (priv->problem.connector->sm & SM_PROBLEM_ACK1) + { + if (priv->problem.connector->sm & SM_PROBLEM_WRAP) + { + if (priv->rcv_count == 0) + hn = G_MAXUINT32; + else if (priv->rcv_count == 1) + hn = 0; + else + hn = 1; + } + else + hn = priv->rcv_count; + } + else if (priv->problem.connector->sm & SM_PROBLEM_OVER) + hn = 2; + else + hn = 0; + + h = g_strdup_printf ("%u", hn); + DEBUG ("Acking %s", h); + wocky_node_set_attribute (top, "h", h); + + if (priv->problem.connector->sm & SM_PROBLEM_ACK1 && priv->rcv_count < 1) + { + wocky_xmpp_connection_send_stanza_async (priv->conn, reply, NULL, + NULL, self); + server_enc_outstanding (self); + DEBUG ("waiting for next stanza from client"); + wocky_xmpp_connection_recv_stanza_async (priv->conn, + priv->cancellable, xmpp_handler, self); + } + else + wocky_xmpp_connection_send_stanza_async (priv->conn, reply, NULL, + finished, self); + + g_object_unref (reply); +} + +static void +handle_error (TestConnectorServer *self, + WockyStanza *xml) +{ + TestConnectorServerPrivate *priv = self->priv; + WockyNode *top = wocky_stanza_get_top_node (xml); + WockyNode *err = wocky_node_get_first_child_ns (top, WOCKY_XMPP_NS_SM3); + + if (priv->problem.connector->sm & SM_PROBLEM_OVER) + g_assert_nonnull (err); + else + g_assert_not_reached (); + + DEBUG ("%s", err->name); + g_object_unref (xml); + wocky_xmpp_connection_send_close_async (priv->conn, NULL, finished, self); +} + static void finished (GObject *source, GAsyncResult *result, @@ -1177,6 +1376,8 @@ xmpp_handler (GObject *source, ns = wocky_node_get_ns (wocky_stanza_get_top_node (xml)); name = wocky_stanza_get_top_node (xml)->name; wocky_stanza_get_type_info (xml, &type, &subtype); + if (type == WOCKY_STANZA_TYPE_IQ && priv->rcv_count >= 0) + priv->rcv_count++; /* if we find a handler, the handler is responsible for listening for the next stanza and setting up the next callback in the chain: */ @@ -1267,6 +1468,13 @@ after_auth (GObject *source, if (!(priv->problem.connector->xmpp & XMPP_PROBLEM_CANNOT_BIND)) wocky_node_add_child_ns (node, "bind", WOCKY_XMPP_NS_BIND); + if (priv->problem.connector->sm) + { + wocky_node_add_child_ns (node, "sm", WOCKY_XMPP_NS_SM3); + /* wait for enable */ + server_enc_outstanding (tcs); + } + priv->state = SERVER_STATE_FEATURES_SENT; server_enc_outstanding (tcs); diff --git a/tests/wocky-test-connector-server.h b/tests/wocky-test-connector-server.h index 31787aaa..ed65acdc 100644 --- a/tests/wocky-test-connector-server.h +++ b/tests/wocky-test-connector-server.h @@ -112,6 +112,20 @@ typedef enum XEP77_PROBLEM_CANCEL_STREAM = CONNPROBLEM(12), } XEP77Problem; +typedef enum +{ + SM_PROBLEM_NONE = 0, + SM_PROBLEM_ENABLED = CONNPROBLEM(0), + SM_PROBLEM_ACK0 = CONNPROBLEM(1), + SM_PROBLEM_ACK1 = CONNPROBLEM(2), + SM_PROBLEM_OVER = CONNPROBLEM(3), + SM_PROBLEM_WRAP = CONNPROBLEM(4), + SM_PROBLEM_ACK0_OVER = (CONNPROBLEM(3) | CONNPROBLEM(1)), + SM_PROBLEM_WRAP0 = (CONNPROBLEM(4) | CONNPROBLEM(1)), + SM_PROBLEM_WRAP1 = (CONNPROBLEM(4) | CONNPROBLEM(2)), + SM_PROBLEM_WRAP0_OVER = (CONNPROBLEM(4) | CONNPROBLEM(3) | CONNPROBLEM(1)), +} SMProblem; + typedef enum { CERT_STANDARD, @@ -134,6 +148,7 @@ typedef struct ServerDeath death; JabberProblem jabber; XEP77Problem xep77; + SMProblem sm; } ConnectorProblem; typedef struct _TestConnectorServer TestConnectorServer; diff --git a/tests/wocky-test-sasl-auth-server.c b/tests/wocky-test-sasl-auth-server.c index 82d10e0f..f343715e 100644 --- a/tests/wocky-test-sasl-auth-server.c +++ b/tests/wocky-test-sasl-auth-server.c @@ -55,6 +55,7 @@ typedef int (*sasl_callback_ft)(void); #else #define SASL_OK 0 +#define SASL_BADPROT -5 #define SASL_BADAUTH -13 #define SASL_NOUSER -20 #define CHECK_SASL_RETURN(x) \ @@ -67,6 +68,14 @@ G_STMT_START { \ #endif +#ifdef WOCKY_SCRAM_NONSTANDARD +#define BINDING_SCRAM_MECH "SCRAM-SHA-512-PLUS" +#define BINDING_SCRAM_ALGO G_CHECKSUM_SHA512 +#else +#define BINDING_SCRAM_MECH "SCRAM-SHA-256-PLUS" +#define BINDING_SCRAM_ALGO G_CHECKSUM_SHA256 +#endif + #if 0 /* signal enum */ enum @@ -720,8 +729,8 @@ handle_auth (TestSaslAuthServer *self, WockyStanza *stanza) { TestSaslAuthServerPrivate *priv = self->priv; guchar *response = NULL; - const gchar *challenge; - unsigned challenge_len; + const gchar *challenge = NULL; + unsigned challenge_len = 0; gsize response_len = 0; int ret; WockyNode *auth = wocky_stanza_get_top_node (stanza); @@ -769,7 +778,7 @@ handle_auth (TestSaslAuthServer *self, WockyStanza *stanza) ret = wocky_strdiff ((gchar *) response, priv->password) ? SASL_BADAUTH : SASL_OK; } - else if (!wocky_strdiff ("SCRAM-SHA-512-PLUS", priv->selected_mech)) + else if (!wocky_strdiff (BINDING_SCRAM_MECH, priv->selected_mech)) { ScramRes res = { self, NULL, FALSE }; GIOStream *ios = NULL; @@ -1036,8 +1045,8 @@ handle_response (TestSaslAuthServer *self, WockyStanza *stanza) { TestSaslAuthServerPrivate * priv = self->priv; guchar *response = NULL; - const gchar *challenge; - unsigned challenge_len; + const gchar *challenge = NULL; + unsigned challenge_len = 0; gsize response_len = 0; int ret; @@ -1056,7 +1065,7 @@ handle_response (TestSaslAuthServer *self, WockyStanza *stanza) &response_len); } - if (!wocky_strdiff ("SCRAM-SHA-512-PLUS", priv->selected_mech)) + if (!wocky_strdiff (BINDING_SCRAM_MECH, priv->selected_mech)) { ScramRes res = { self, NULL, FALSE }; @@ -1323,10 +1332,10 @@ test_sasl_auth_server_new (GIOStream *stream, gchar *mech, priv->mech = g_strdup (mech); priv->problem = problem; - if (!wocky_strdiff ("SCRAM-SHA-512-PLUS", mech)) + if (!wocky_strdiff (BINDING_SCRAM_MECH, mech)) { priv->scram = g_object_new (WOCKY_TYPE_SASL_SCRAM, "server", servername, - "hash-algo", G_CHECKSUM_SHA512, NULL); + "hash-algo", BINDING_SCRAM_ALGO, NULL); } if (start) @@ -1415,6 +1424,7 @@ test_sasl_auth_server_set_mechs (GObject *obj, TestSaslAuthServer *self = TEST_SASL_AUTH_SERVER (obj); TestSaslAuthServerPrivate *priv = self->priv; WockyNode *mechnode = NULL; + const gchar *mech = (must) ? must : priv->mech; gboolean hazmech = FALSE; if (priv->problem != SERVER_PROBLEM_NO_SASL) @@ -1426,11 +1436,6 @@ test_sasl_auth_server_set_mechs (GObject *obj, { /* lalala */ } - else if (priv->mech != NULL) - { - wocky_node_add_child_with_content (mechnode, "mechanism", - priv->mech); - } else { const gchar *mechs; @@ -1448,18 +1453,22 @@ test_sasl_auth_server_set_mechs (GObject *obj, mechlist = g_strsplit (mechs, "\n", -1); for (tmp = mechlist; *tmp != NULL; tmp++) { + if (priv->mech && wocky_strdiff (priv->mech, *tmp)) + continue; + wocky_node_add_child_with_content (mechnode, "mechanism", *tmp); - if (!hazmech && !wocky_strdiff (*tmp, must)) + + if (!hazmech && !wocky_strdiff (*tmp, mech)) hazmech = TRUE; } g_strfreev (mechlist); - if (!hazmech && must != NULL - && g_str_has_prefix (must, "SCRAM-SHA-")) + if (!hazmech && mech != NULL + && g_str_has_prefix (mech, "SCRAM-SHA-")) { /* as said before, this is ridiculous so let's fix that */ - if (g_str_has_prefix (must, "SCRAM-SHA-256")) + if (g_str_has_prefix (mech, "SCRAM-SHA-256")) { if (priv->scram == NULL) priv->scram = g_object_new (WOCKY_TYPE_SASL_SCRAM, @@ -1467,8 +1476,23 @@ test_sasl_auth_server_set_mechs (GObject *obj, "hash-algo", G_CHECKSUM_SHA256, NULL); wocky_node_add_child_with_content (mechnode, - "mechanism", must); + "mechanism", mech); } + else if (g_str_has_prefix (mech, "SCRAM-SHA-512")) + { + if (priv->scram == NULL) + priv->scram = g_object_new (WOCKY_TYPE_SASL_SCRAM, + "server", "whatever", + "hash-algo", G_CHECKSUM_SHA512, + NULL); + wocky_node_add_child_with_content (mechnode, + "mechanism", mech); + } + } + else if (!hazmech && priv->mech) + { + wocky_node_add_child_with_content (mechnode, + "mechanism", priv->mech); } } } diff --git a/tests/wocky-test-sasl-auth.c b/tests/wocky-test-sasl-auth.c index 83e4aabb..97a677a9 100644 --- a/tests/wocky-test-sasl-auth.c +++ b/tests/wocky-test-sasl-auth.c @@ -11,6 +11,12 @@ #include +#ifdef WOCKY_SCRAM_NONSTANDARD +#define BINDING_SCRAM_MECH "SCRAM-SHA-512-PLUS" +#else +#define BINDING_SCRAM_MECH "SCRAM-SHA-256-PLUS" +#endif + typedef struct { gchar *description; gchar *mech; @@ -280,8 +286,8 @@ main (int argc, 0, 0, SERVER_PROBLEM_FINAL_DATA_IN_SUCCESS, FALSE, FALSE, "test", "test123", NULL }, - SUCCESS("/xmpp-sasl/scram-sha2-plus-multistep", "SCRAM-SHA-512-PLUS", TRUE), - { "/xmpp-sasl/scram-sha2-final-data-in-success", "SCRAM-SHA-512-PLUS", TRUE, + SUCCESS("/xmpp-sasl/scram-sha2-plus-multistep", BINDING_SCRAM_MECH, TRUE), + { "/xmpp-sasl/scram-sha2-final-data-in-success", BINDING_SCRAM_MECH, TRUE, 0, 0, SERVER_PROBLEM_FINAL_DATA_IN_SUCCESS, FALSE, FALSE, "test", "test123", NULL }, @@ -301,7 +307,7 @@ main (int argc, { "/xmpp-sasl/wrong-username-md5", "DIGEST-MD5", TRUE, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, SERVER_PROBLEM_INVALID_USERNAME, TRUE, FALSE, "test", "test123" }, - { "/xmpp-sasl/wrong-username-sha2", "SCRAM-SHA-512-PLUS", TRUE, + { "/xmpp-sasl/wrong-username-sha2", BINDING_SCRAM_MECH, TRUE, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, SERVER_PROBLEM_INVALID_USERNAME, TRUE, FALSE, "test", "test123" }, @@ -311,7 +317,7 @@ main (int argc, { "/xmpp-sasl/wrong-password-md5", "DIGEST-MD5", TRUE, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, SERVER_PROBLEM_INVALID_PASSWORD, FALSE, TRUE, "test", "test123" }, - { "/xmpp-sasl/wrong-password-sha2", "SCRAM-SHA-512-PLUS", TRUE, + { "/xmpp-sasl/wrong-password-sha2", BINDING_SCRAM_MECH, TRUE, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, SERVER_PROBLEM_INVALID_PASSWORD, FALSE, TRUE, "test", "test123" }, diff --git a/wocky/meson.build b/wocky/meson.build index 2209a474..18bf4d21 100644 --- a/wocky/meson.build +++ b/wocky/meson.build @@ -218,10 +218,11 @@ gir = find_program('g-ir-scanner', required: get_option('introspection').enabled if gir.found() wocky_gir = gnome.generate_gir(wocky_so, sources: [handwritten_headers, handwritten_sources, wocky_marshals, wocky_enums], - dependencies: wocky_deps, + dependencies: [wocky_deps, wocky_so], + includes : ['GLib-2.0', 'GObject-2.0', 'GModule-2.0', 'Gio-2.0'], namespace: 'Wocky', nsversion: '0', - symbol_prefix: ['wocky'], + symbol_prefix: ['wocky', 'jingle', 'sasl'], header: 'wocky.h', install: build_gir, extra_args: wocky_cflags diff --git a/wocky/wocky-auth-registry.c b/wocky/wocky-auth-registry.c index 721dd64d..900bd70d 100644 --- a/wocky/wocky-auth-registry.c +++ b/wocky/wocky-auth-registry.c @@ -179,12 +179,32 @@ wocky_auth_registry_class_init (WockyAuthRegistryClass *klass) object_class->constructed = wocky_auth_registry_constructed; object_class->get_property = wocky_auth_registry_get_property; object_class->set_property = wocky_auth_registry_set_property; + + + /** + * WockyAuthRegistry:tls-binding-type: + * + * A property setting the type of the tls channel binding discovered + * for the current session. Is set byt WockySaslAuth after receiving + * supported mechanisms from the peer. + * + * Since: 0.19.0 + */ g_object_class_install_property (object_class, PROP_CB_TYPE, g_param_spec_enum ("tls-binding-type", "tls channel binding type", "The type of the TLS Channel Binding to use in SASL negotiation", WOCKY_TYPE_TLS_BINDING_TYPE, WOCKY_TLS_BINDING_DISABLED, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + /** + * WockyAuthRegistry:tls-binding-data: + * + * If #WockyAuthRegistry:tls-binding-type is above WOCKY_TLS_BINDING_NONE + * must contain actual Base64 encoded tls channel binding data, %NULL + * otherwise. + * + * Since: 0.19.0 + */ g_object_class_install_property (object_class, PROP_CB_DATA, g_param_spec_string ("tls-binding-data", "tls channel binding data", "Base64 encoded TLS Channel binding data for the set type", NULL, @@ -225,6 +245,19 @@ wocky_auth_registry_has_mechanism ( return (g_slist_find_custom (list, mech, (GCompareFunc) g_strcmp0) != NULL); } +GType +wocky_auth_registry_start_data_get_type (void) +{ + static GType t = 0; + + if (G_UNLIKELY (t == 0)) + t = g_boxed_type_register_static ("WockyAuthRegistryStartData", + (GBoxedCopyFunc) wocky_auth_registry_start_data_dup, + (GBoxedFreeFunc) wocky_auth_registry_start_data_free); + + return t; +} + WockyAuthRegistryStartData * wocky_auth_registry_start_data_new (const gchar *mechanism, const GString *initial_response) @@ -276,8 +309,10 @@ wocky_auth_registry_select_handler (WockyAuthRegistry *self, gboolean is_plus; GChecksumType algo; } scram_handlers[] = { +#ifdef WOCKY_AUTH_MECH_SASL_SCRAM_SHA_512 { WOCKY_AUTH_MECH_SASL_SCRAM_SHA_512_PLUS, TRUE, G_CHECKSUM_SHA512 }, { WOCKY_AUTH_MECH_SASL_SCRAM_SHA_512, FALSE, G_CHECKSUM_SHA512 }, +#endif #ifdef WOCKY_AUTH_MECH_SASL_SCRAM_SHA_384 { WOCKY_AUTH_MECH_SASL_SCRAM_SHA_384_PLUS, TRUE, G_CHECKSUM_SHA384 }, { WOCKY_AUTH_MECH_SASL_SCRAM_SHA_384, FALSE, G_CHECKSUM_SHA384 }, @@ -445,6 +480,24 @@ wocky_auth_registry_start_auth_async_func (WockyAuthRegistry *self, g_object_unref (task); } +/** + * wocky_auth_registry_start_auth_async: + * @self: a #WockyAuthRegistry instance + * @mechanisms: (element-type utf8): a list of discovered mechanisms + * @allow_plain: Whether we allow clear-text mechanisms + * @is_secure_channel: whether registry is created on secure channel + * @username: Identity (username) to use for authentication + * @password: Secret (password) to use for authentication + * @server: server identity to use (if required by selected method) + * @session_id: XMPP session id to use (if required by selected method) + * @callback: a callback to call once authentication is finished + * @user_data: a data to pass to the callback + * + * Starts async authentication on the associated channel, using passed + * credentials, server and connection details. Attempts to find best + * suitable handler for the given mechanisms, preferring either provided + * custom handler or the strongest security mechanism supported. + */ void wocky_auth_registry_start_auth_async (WockyAuthRegistry *self, GSList *mechanisms, @@ -564,6 +617,7 @@ wocky_auth_registry_success_async_func (WockyAuthRegistry *self, g_task_return_boolean (task, TRUE); g_object_unref (task); + g_clear_object (&priv->handler); } @@ -621,7 +675,7 @@ wocky_auth_registry_add_handler (WockyAuthRegistry *self, * wocky_auth_registry_supports_one_of: * @self: a #WockyAuthRegistry * @allow_plain: Whether auth in plain text is allowed - * @mechanisms: a #GSList of gchar* of auth mechanisms + * @mechanisms: (element-type utf8): a #GSList of gchar* of auth mechanisms * * Checks whether at least one of @mechanisms is supported by Wocky. At present, * Wocky itself only implements password-based authentication mechanisms. diff --git a/wocky/wocky-auth-registry.h b/wocky/wocky-auth-registry.h index 28e89a53..e0776fff 100644 --- a/wocky/wocky-auth-registry.h +++ b/wocky/wocky-auth-registry.h @@ -62,12 +62,14 @@ typedef enum #define WOCKY_AUTH_MECH_SASL_SCRAM_SHA_1_PLUS "SCRAM-SHA-1-PLUS" #define WOCKY_AUTH_MECH_SASL_SCRAM_SHA_256 "SCRAM-SHA-256" #define WOCKY_AUTH_MECH_SASL_SCRAM_SHA_256_PLUS "SCRAM-SHA-256-PLUS" +#ifdef WOCKY_SCRAM_NONSTANDARD #define WOCKY_AUTH_MECH_SASL_SCRAM_SHA_512 "SCRAM-SHA-512" #define WOCKY_AUTH_MECH_SASL_SCRAM_SHA_512_PLUS "SCRAM-SHA-512-PLUS" #if GLIB_VERSION_CUR_STABLE >= GLIB_VERSION_2_52 #define WOCKY_AUTH_MECH_SASL_SCRAM_SHA_384 "SCRAM-SHA-384" #define WOCKY_AUTH_MECH_SASL_SCRAM_SHA_384_PLUS "SCRAM-SHA-384-PLUS" #endif +#endif /** * WockyTLSBindingType @@ -86,6 +88,7 @@ typedef enum WOCKY_TLS_BINDING_TLS_UNIQUE, WOCKY_TLS_BINDING_TLS_SERVER_END_POINT, WOCKY_TLS_BINDING_TLS_EXPORTER, + /*< private >*/ WOCKY_TLS_BINDING_INVALID_TYPE } WockyTLSBindingType; @@ -101,6 +104,10 @@ typedef struct { GString *initial_response; } WockyAuthRegistryStartData; +#define WOCKY_TYPE_AUTH_REGISTRY_START_DATA \ + (wocky_auth_registry_start_data_get_type ()) +GType wocky_auth_registry_start_data_get_type (void); + #define WOCKY_TYPE_AUTH_REGISTRY wocky_auth_registry_get_type() #define WOCKY_AUTH_REGISTRY(obj) \ diff --git a/wocky/wocky-bare-contact.c b/wocky/wocky-bare-contact.c index 29ece8e2..2f89e293 100644 --- a/wocky/wocky-bare-contact.c +++ b/wocky/wocky-bare-contact.c @@ -679,8 +679,8 @@ wocky_bare_contact_remove_group (WockyBareContact *self, * * Convenience function to obtain a copy of the given #WockyBareContact. * - * Returns: a newly created #WockyBareContact which is a copy of the given - * one. + * Returns: (transfer full): a newly created #WockyBareContact which is a copy + * of the given one. */ WockyBareContact * wocky_bare_contact_copy (WockyBareContact *contact) @@ -738,7 +738,8 @@ wocky_bare_contact_add_resource (WockyBareContact *self, * Gets a #GSList of all the contact's resources. * You should call #g_slist_free on the list when done with it. * - * Returns: a #GSList of #WockyResourceContact objects. + * Returns: (transfer container)(element-type WockyResourceContact): a #GSList + * of #WockyResourceContact objects. */ GSList * wocky_bare_contact_get_resources (WockyBareContact *self) diff --git a/wocky/wocky-c2s-porter.c b/wocky/wocky-c2s-porter.c index 7214337c..c2a12dde 100644 --- a/wocky/wocky-c2s-porter.c +++ b/wocky/wocky-c2s-porter.c @@ -37,6 +37,7 @@ #include #include #include +#include #ifdef HAVE_UNISTD_H # include @@ -48,6 +49,7 @@ #include "wocky-utils.h" #include "wocky-namespaces.h" #include "wocky-contact-factory.h" +#include "wocky-connector.h" #define WOCKY_DEBUG_FLAG WOCKY_DEBUG_PORTER #include "wocky-debug-internal.h" @@ -61,6 +63,7 @@ enum PROP_FULL_JID, PROP_BARE_JID, PROP_RESOURCE, + PROP_CONNECTOR }; /* private structure */ @@ -76,8 +79,10 @@ struct _WockyC2SPorterPrivate /* Queue of (sending_queue_elem *) */ GQueue *sending_queue; + /* Queue of sent WockyStanza's waiting for SM Ack */ + GQueue *unacked_queue; GCancellable *receive_cancellable; - gboolean sending_whitespace_ping; + gboolean sending_blocked; GTask *close_task; gboolean waiting_to_close; @@ -102,6 +107,9 @@ struct _WockyC2SPorterPrivate /* List of (owned WockyStanza *) */ GQueue queueable_stanza_patterns; + WockyPorterSmCtx *sm; + WockyConnector *connector; /* is set from connection when sm is discovered */ + WockyXmppConnection *connection; }; @@ -329,6 +337,9 @@ static gboolean handle_iq_reply (WockyPorter *porter, static void remote_connection_closed (WockyC2SPorter *self, const GError *error); +static void wocky_porter_sm_reset (WockyC2SPorter *self); +static gboolean wocky_porter_sm_handle (WockyC2SPorter *self, WockyNode *node); + static void wocky_c2s_porter_init (WockyC2SPorter *self) { @@ -337,6 +348,7 @@ wocky_c2s_porter_init (WockyC2SPorter *self) self->priv = priv; priv->sending_queue = g_queue_new (); + priv->unacked_queue = NULL; priv->handlers_by_id = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) stanza_handler_free); @@ -385,6 +397,11 @@ wocky_c2s_porter_set_property (GObject *object, g_free (node); break; + case PROP_CONNECTOR: + g_clear_object (&priv->connector); + priv->connector = g_value_dup_object (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -419,6 +436,10 @@ wocky_c2s_porter_get_property (GObject *object, g_value_set_string (value, priv->resource); break; + case PROP_CONNECTOR: + g_value_set_object (value, priv->connector); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -456,6 +477,15 @@ wocky_c2s_porter_constructed (GObject *object) g_assert (priv->connection != NULL); + priv->connector = g_object_steal_data (G_OBJECT (priv->connection), + WOCKY_XMPP_NS_SM3); + if (priv->connector != NULL) + { + priv->sm = g_new0 (WockyPorterSmCtx, 1); + priv->sm->enabled = TRUE; + wocky_porter_sm_reset (self); + } + /* Register the IQ reply handler */ wocky_porter_register_handler_from_anyone (WOCKY_PORTER (self), WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_RESULT, @@ -482,6 +512,7 @@ wocky_c2s_porter_class_init ( WockyC2SPorterClass *wocky_c2s_porter_class) { GObjectClass *object_class = G_OBJECT_CLASS (wocky_c2s_porter_class); + GParamSpec *spec; object_class->constructed = wocky_c2s_porter_constructed; object_class->set_property = wocky_c2s_porter_set_property; @@ -497,6 +528,19 @@ wocky_c2s_porter_class_init ( PROP_BARE_JID, "bare-jid"); g_object_class_override_property (object_class, PROP_RESOURCE, "resource"); + /** + * WockyC2SPorter:connector: + * + * A #WockyConnector instance, the instance which was used to establish + * current connection and which is supposed to be passed by the connector + * itself as XmppConnection data when connector discovers SM resumable + * connection. The property could be reset by the porter owner if automatic + * connection resumption is not desirable. + */ + spec = g_param_spec_object ("connector", "WockyConnector", + "The connector which created this connection", WOCKY_TYPE_CONNECTOR, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, PROP_CONNECTOR, spec); } void @@ -526,6 +570,13 @@ wocky_c2s_porter_dispose (GObject *object) g_clear_object (&(priv->force_close_task)); g_clear_object (&(priv->force_close_cancellable)); + if (priv->sm) + { + priv->sm->enabled = FALSE; + wocky_porter_sm_reset (self); + g_clear_pointer (&priv->sm, g_free); + } + if (G_OBJECT_CLASS (wocky_c2s_porter_parent_class)->dispose) G_OBJECT_CLASS (wocky_c2s_porter_parent_class)->dispose (object); } @@ -570,7 +621,7 @@ wocky_c2s_porter_finalize (GObject *object) * * Convenience function to create a new #WockyC2SPorter. * - * Returns: a new #WockyPorter. + * Returns: (transfer full): a new #WockyPorter. */ WockyPorter * wocky_c2s_porter_new (WockyXmppConnection *connection, @@ -629,7 +680,7 @@ sending_in_progress (WockyC2SPorter *self) WockyC2SPorterPrivate *priv = self->priv; return g_queue_get_length (priv->sending_queue) > 0 || - priv->sending_whitespace_ping; + priv->sending_blocked; } static void @@ -645,6 +696,16 @@ close_if_waiting (WockyC2SPorter *self) } } +static gboolean +request_ack_in_idle (gpointer data) +{ + WockyC2SPorter *self = WOCKY_C2S_PORTER (data); + + wocky_porter_sm_handle (self, NULL); + + return FALSE; +} + static void send_stanza_cb (GObject *source, GAsyncResult *res, @@ -674,6 +735,23 @@ send_stanza_cb (GObject *source, g_task_return_boolean (elem->task, TRUE); + if (priv->sm && priv->sm->enabled) + { + WockyStanzaType st; + wocky_stanza_get_type_info (elem->stanza, &st, NULL); + if (st == WOCKY_STANZA_TYPE_MESSAGE + || st == WOCKY_STANZA_TYPE_PRESENCE + || st == WOCKY_STANZA_TYPE_IQ) + { + priv->sm->snt_count++; + DEBUG ("Queuing stanza %s", + wocky_stanza_get_top_node (elem->stanza)->name); + g_queue_push_tail (priv->unacked_queue, + g_object_ref (elem->stanza)); + g_idle_add (request_ack_in_idle, self); + } + } + sending_queue_elem_free (elem); if (g_queue_get_length (priv->sending_queue) > 0) @@ -727,7 +805,7 @@ wocky_c2s_porter_send_async (WockyPorter *porter, g_queue_push_tail (priv->sending_queue, elem); if (g_queue_get_length (priv->sending_queue) == 1 && - !priv->sending_whitespace_ping) + !priv->sending_blocked && priv->connection) { send_head_stanza (self); } @@ -929,13 +1007,27 @@ handle_stanza (WockyC2SPorter *self, wocky_stanza_get_type_info (stanza, &type, &sub_type); + if (priv->sm && priv->sm->enabled + && (type == WOCKY_STANZA_TYPE_MESSAGE + || type == WOCKY_STANZA_TYPE_PRESENCE + || type == WOCKY_STANZA_TYPE_IQ)) + { + priv->sm->rcv_count++; + } /* The from attribute of the stanza need not always be present, for example * when receiving roster items, so don't enforce it. */ from = wocky_stanza_get_from (stanza); if (from == NULL) { + WockyNode *top_node = wocky_stanza_get_top_node (stanza); + + g_assert (top_node != NULL); + is_from_server = TRUE; + + if (wocky_node_has_ns (top_node, WOCKY_XMPP_NS_SM3)) + handled = wocky_porter_sm_handle (self, top_node); } else if (wocky_decode_jid (from, &node, &domain, &resource)) { @@ -1251,6 +1343,455 @@ connection_force_close_cb (GObject *source, g_object_unref (self); } +/** + * wocky_c2s_porter_get_sm_ctx: + * @self: a #WockyC2SPorter + * + * This call is rather for the test units - to enable visibility of the + * StreamManagement internals in WockyC2SPorter. However it may be used + * for other purposes as well. It returns the SM context structure which + * holds SM internal state. Do not modify the SM counters otherwise the + * connection will most likely be terminated. + * + * Returns: (nullable): #WockyPorterSmCtx of the given porter or %NULL + * if SM was not discovered on the connection. + */ +const WockyPorterSmCtx * +wocky_c2s_porter_get_sm_ctx (WockyC2SPorter *self) +{ + WockyC2SPorterPrivate *priv = wocky_c2s_porter_get_instance_private (self); + + return priv->sm; +} + +void +wocky_c2s_porter_resume (WockyC2SPorter *self, + WockyXmppConnection *connection) +{ + WockyC2SPorterPrivate *priv = wocky_c2s_porter_get_instance_private (self); + + g_assert (priv->receive_cancellable); + g_assert (connection); + + priv->connection = connection; + priv->sending_blocked = FALSE; + receive_stanza (self); +} + +static void +connection_reconnected_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + WockyC2SPorter *self = WOCKY_C2S_PORTER (user_data); + WockyC2SPorterPrivate *priv = wocky_c2s_porter_get_instance_private (self); + WockyConnector *ctr = WOCKY_CONNECTOR (source); + g_autofree gchar *sid = NULL; + GError *err = NULL; + + if ((priv->connection = wocky_connector_connect_finish (ctr, + res, &priv->full_jid, &sid, &err)) == NULL) + { + /* this is the last of them, no way out */ + remote_connection_closed (self, err); + g_error_free (err); + return; + } + priv->sending_blocked = FALSE; + /* We can only be here if connection supports SM and resume failed, + * hence turn SM back on and continue */ + priv->sm->enabled = TRUE; + priv->connector = g_object_ref (ctr); + wocky_porter_sm_reset (self); + /* We have reset the state already, just continue as if nothing happened */ + g_object_notify (G_OBJECT (self), "connection"); + g_object_notify (G_OBJECT (self), "full-jid"); + g_signal_emit_by_name (self, "reconnected", priv->full_jid, sid); + receive_stanza (self); +} + +static void +resume_connection_failed (WockyC2SPorter *self, + WockyConnector *ctr) +{ + WockyC2SPorterPrivate *priv = wocky_c2s_porter_get_instance_private (self); + gboolean ret = FALSE; + + g_signal_emit_by_name (self, "resume-failed", &ret); + if (!ret) + { + DEBUG ("Got stop signal, handing over the reconnection vector"); + return; + } + + DEBUG ("Resuming session does not exist, proceed with %p[%p]", ctr, priv->connector); + + /* when we're managed our connector will likely be NULL */ + if (priv->connector != ctr) + return; + + /* Resume has returned failed:item-not-found, we can continue with the bind + * but first we need to reset the state */ + flush_unimportant_queue (self); + priv->sm->enabled = FALSE; + wocky_porter_sm_reset (self); + + /* by doing this we're risking to desync gabble-connection and its + * wocky-session from porter - we may have different resource after bind */ + wocky_connector_continue_async (ctr, priv->receive_cancellable, + connection_reconnected_cb, self); +} + +static void +resume_connection_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + WockyC2SPorter *self = WOCKY_C2S_PORTER (user_data); + WockyC2SPorterPrivate *priv = wocky_c2s_porter_get_instance_private (self); + WockyConnector *ctr = WOCKY_CONNECTOR (source); + GError *err = NULL; + + if ((priv->connection = wocky_connector_resume_finish (ctr, res, &err)) == NULL) + { + DEBUG ("Resumption failed: %s", err->message); + terminate_sending_operations (self, err); + + /* We may have hard error (connection failed) and soft error (resumption + * failed). Former should cause retry or stop, later continue or bail + * depending on whether we're in managed or self-driven mode */ + if (g_error_matches (err, WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_ITEM_NOT_FOUND)) + resume_connection_failed (self, ctr); + else if (priv->connector == NULL) + remote_connection_closed (self, err); + else + { + DEBUG ("Reconnecting on next heartbeat"); + priv->sending_blocked = FALSE; + } + + g_error_free (err); + } + else + { + /* We should now read `resumed` nonza and handle resumption resync. */ + DEBUG ("Connection resumed, try to resync"); + wocky_c2s_porter_resume (self, priv->connection); + } +} + +static void +resume_connection (WockyC2SPorter *self) +{ + WockyC2SPorterPrivate *priv = wocky_c2s_porter_get_instance_private (self); + WockyStanza *resume = wocky_stanza_new ("resume", WOCKY_XMPP_NS_SM3); + WockyNode *rn = wocky_stanza_get_top_node (resume); + gchar *val = g_strdup_printf ("%zu", priv->sm->rcv_count); + gboolean ret = TRUE; + + g_assert (priv->sm); + g_assert (priv->sm->id); + + wocky_node_set_attribute (rn, "h", val); + wocky_node_set_attribute (rn, "previd", priv->sm->id); + + g_clear_object (&priv->connection); + g_object_notify (G_OBJECT (self), "connection"); + priv->sending_blocked = TRUE; + DEBUG ("Attempting to resume sm %s:%s with connector %p", + priv->sm->id, val, priv->connector); + g_free (val); + + g_signal_emit_by_name (self, "resuming", resume, &ret); + if (ret) + { + g_assert (priv->connector); + wocky_connector_resume_async (priv->connector, resume, + priv->receive_cancellable, resume_connection_cb, self); + } + else + DEBUG ("Got stop signal, skipping auto-resume"); + + g_object_unref (resume); +} + +static void +wocky_porter_resume_done (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + WockyC2SPorter *self = WOCKY_C2S_PORTER (source); + GError *error = NULL; + + if (wocky_c2s_porter_send_finish (WOCKY_PORTER (self), res, &error)) + return g_signal_emit_by_name (G_OBJECT (self), "resume-done"); + + if (error->domain != WOCKY_XMPP_CONNECTION_ERROR + || error->code == WOCKY_XMPP_CONNECTION_ERROR_EOS) + resume_connection (self); + else + remote_connection_closed (self, error); + g_error_free (error); +} + +static void +wocky_porter_sm_reset (WockyC2SPorter *self) +{ + WockyC2SPorterPrivate *priv = wocky_c2s_porter_get_instance_private (self); + + if (priv->sm == NULL) + return; + + if (priv->sm->enabled) + { + if (G_UNLIKELY (priv->unacked_queue)) + g_queue_free_full (priv->unacked_queue, g_object_unref); + priv->unacked_queue = g_queue_new (); + } + else + { + g_clear_object (&priv->connector); + + if (priv->unacked_queue) + { + g_queue_free_full (priv->unacked_queue, g_object_unref); + priv->unacked_queue = NULL; + } + priv->sm->resumable = FALSE; + } + + priv->sm->snt_count = priv->sm->snt_acked = priv->sm->rcv_count = 0; + g_clear_pointer (&priv->sm->id, g_free); + g_clear_pointer (&priv->sm->location, g_free); + priv->sm->resumable = FALSE; + priv->sm->timeout = 600; +} + +static void +wocky_porter_sm_error_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + WockyC2SPorter *self = WOCKY_C2S_PORTER (source); + WockyC2SPorterPrivate *priv = wocky_c2s_porter_get_instance_private (self); + GError *error = NULL; + gboolean ret; + + if (user_data) + { + ret = wocky_porter_send_finish (WOCKY_PORTER (self), res, &error); + if (ret) + wocky_porter_close_async (WOCKY_PORTER (self), + priv->receive_cancellable, wocky_porter_sm_error_cb, NULL); + } + else + { + ret = wocky_porter_close_finish (WOCKY_PORTER (self), res, &error); + g_signal_emit_by_name (self, "remote-error", WOCKY_XMPP_STREAM_ERROR, + WOCKY_XMPP_STREAM_ERROR_UNDEFINED_CONDITION, + "Server acknowledged more stanzas than we have sent"); + } +} + +/* We need to consider normal monotonic increase and uint32 wrap conditions */ +#define ACK_WINDOW(start, stop) ((start <= stop) \ + ? (stop - start) \ + : ((G_MAXUINT32 - start + 1) + stop)) +#define ACK_WINDOW_MAX 10 + +static gboolean +wocky_porter_sm_handle_h (WockyC2SPorter *self, + WockyNode *node) +{ + WockyC2SPorterPrivate *priv = wocky_c2s_porter_get_instance_private (self); + const gchar *val = wocky_node_get_attribute (node, "h"); + gsize snt = 0; + + /* TODO below conditions are rather fatal, need to terminate the sream */ + if (val == NULL) + { + g_warning ("Missing 'h' attribute, we should better err this stream"); + return FALSE; + } + + snt = strtoul (val, NULL, 10); + if (snt == ULONG_MAX && errno == ERANGE) + { + g_warning ("Invalid number, cannot convert h value[%s] to gsize", val); + return FALSE; + } + + if (ACK_WINDOW (priv->sm->snt_acked, snt) > ACK_WINDOW (priv->sm->snt_acked, + priv->sm->snt_count)) + { + /* Try to send a stanza using the closing porter */ + WockyStanza *error = wocky_stanza_build (WOCKY_STANZA_TYPE_STREAM_ERROR, + WOCKY_STANZA_SUB_TYPE_NONE, NULL, NULL, + ':', WOCKY_XMPP_NS_STREAM, + '(', "undefined-condition", + ':', WOCKY_XMPP_NS_STREAMS, ')', + '(', "handled-count-too-high", + ':', WOCKY_XMPP_NS_SM3, ')', + NULL); + + DEBUG ("Invalid acknowledgement %zu, must be between %zu and %zu", + snt, priv->sm->snt_acked, priv->sm->snt_count); + wocky_porter_send_async (WOCKY_PORTER (self), error, + priv->receive_cancellable, wocky_porter_sm_error_cb, self); + + g_object_unref (error); + return FALSE; + } + + DEBUG ("Acking %zu stanzas handled by the server", snt); + priv->sm->snt_acked = snt; + /* now we can head-drop stanzas from unacked_queue till its length is + * (snt_count - snt_acked) */ + while (g_queue_get_length (priv->unacked_queue) + > ACK_WINDOW (priv->sm->snt_acked, priv->sm->snt_count)) + { + WockyStanza *s = g_queue_pop_head (priv->unacked_queue); + g_assert (s); + g_object_unref (s); + } + DEBUG ("After ack %u(%zu) stanzas left in the queue", + g_queue_get_length (priv->unacked_queue), + ACK_WINDOW (priv->sm->snt_acked, priv->sm->snt_count)); + return TRUE; +} + +static gboolean +wocky_porter_sm_handle (WockyC2SPorter *self, + WockyNode *node) +{ + WockyC2SPorterPrivate *priv = wocky_c2s_porter_get_instance_private (self); + + if (G_UNLIKELY(!priv->sm->enabled)) + { + g_warning ("Received SM nonza %s while SM is disabled", node->name); + return FALSE; + } + + if (node == NULL) + { + /* a probe for request - fire one if we have breached the max or fire an + * early notice when we are in the middle of it */ + if (ACK_WINDOW (priv->sm->snt_acked, priv->sm->snt_count) == ACK_WINDOW_MAX/2 + || ACK_WINDOW (priv->sm->snt_acked, priv->sm->snt_count) > ACK_WINDOW_MAX) + { + WockyStanza *r = wocky_stanza_new ("r", WOCKY_XMPP_NS_SM3); + if (priv->sm->reqs == 0) + { + wocky_porter_send (WOCKY_PORTER (self), r); + priv->sm->reqs++; + } + g_object_unref (r); + return TRUE; + } + } + else if (node->name[0] == 'r' && node->name[1] == '\0') + { + WockyStanza *a = wocky_stanza_new ("a", WOCKY_XMPP_NS_SM3); + WockyNode *an = wocky_stanza_get_top_node (a); + gchar *val = g_strdup_printf ("%zu", priv->sm->rcv_count); + + wocky_node_set_attribute (an, "h", val); + g_free (val); + + wocky_porter_send (WOCKY_PORTER (self), a); + g_object_unref (a); + + return TRUE; + } + else if (node->name[0] == 'a' && node->name[1] == '\0') + { + if (priv->sm->reqs > 0) + priv->sm->reqs--; + return wocky_porter_sm_handle_h (self, node); + } + else if (!g_strcmp0 (node->name, "enabled")) + { + const gchar *val; + if ((val = wocky_node_get_attribute (node, "id")) != NULL) + priv->sm->id = g_strdup (val); + + if ((val = wocky_node_get_attribute (node, "max")) != NULL) + priv->sm->timeout = strtoul (val, NULL, 10); + + if ((val = wocky_node_get_attribute (node, "resume")) != NULL) + priv->sm->resumable = (g_strcmp0 (val, "true") == 0); + + if ((val = wocky_node_get_attribute (node, "location")) != NULL) + priv->sm->location = g_strdup (val); + + DEBUG ("SM on connection %p is enabled", priv->connection); + + return TRUE; + } + else if (!g_strcmp0 (node->name, "resumed")) + { + const gchar *val; + WockyStanza *smr; + + if (G_UNLIKELY((val = wocky_node_get_attribute (node, "previd")) == NULL + || g_strcmp0 (val, priv->sm->id))) + { + /* this must not happen */ + GError err = {WOCKY_XMPP_STREAM_ERROR, + WOCKY_XMPP_STREAM_ERROR_INVALID_ID, + "Resumed SM-ID mismatch"}; + remote_connection_closed (self, &err); + return TRUE; + } + + if (G_UNLIKELY ((val = wocky_node_get_attribute (node, "previd")) == NULL + || !wocky_porter_sm_handle_h (self, node))) + { + /* this must not happen */ + GError err = {WOCKY_XMPP_STREAM_ERROR, + WOCKY_XMPP_STREAM_ERROR_BAD_FORMAT, + "Resumed nonza has wrong or missing 'h' attribute"}; + remote_connection_closed (self, &err); + return TRUE; + } + + /* passed all sanity checks and trimmed the queue, let's move its + * content to the sending one */ + DEBUG ("Moving %u stanzas from SM into sending queue", + g_queue_get_length (priv->unacked_queue)); + /* Note - they will be pushed back to the unacked_queue once sent over + * the connection. Thus we need to reset snt_count to prevent its + * creeping */ + priv->sm->snt_count = priv->sm->snt_acked; + while (!g_queue_is_empty (priv->unacked_queue)) + wocky_c2s_porter_send_async (WOCKY_PORTER (self), + g_queue_pop_head (priv->unacked_queue), NULL, NULL, NULL); + + /* The signal processign may inject some stanzas breaking the order + * of stanzas and thus violating RFC6120. Hence signaling only after + * the SM queue is flushed into sending queue */ + DEBUG ("Emitting resumed signal after SM queue is flushed"); + g_signal_emit_by_name (self, "resumed"); + + /* Add `r` to the end of the queue and track its progress */ + smr = wocky_stanza_new ("r", WOCKY_XMPP_NS_SM3); + wocky_c2s_porter_send_async (WOCKY_PORTER (self), smr, + priv->receive_cancellable, wocky_porter_resume_done, NULL); + g_object_unref (smr); + return TRUE; + } + else if (!g_strcmp0 (node->name, "failed")) + { + DEBUG ("SM on connection %p has failed", priv->connection); + priv->sm->enabled = FALSE; + wocky_porter_sm_reset (self); + } + else + g_warning ("Unknown SM nonza '%s' received", node->name); + + return FALSE; +} + static void stanza_received_cb (GObject *source, GAsyncResult *res, @@ -1295,7 +1836,12 @@ stanza_received_cb (GObject *source, } else { - remote_connection_closed (self, error); + if (priv->sm && priv->sm->resumable + && (error->domain != WOCKY_XMPP_CONNECTION_ERROR + || error->code == WOCKY_XMPP_CONNECTION_ERROR_EOS)) + resume_connection (self); + else + remote_connection_closed (self, error); } g_error_free (error); @@ -1577,9 +2123,9 @@ wocky_c2s_porter_register_handler_from_anyone_by_stanza ( * %WOCKY_PORTER_HANDLER_PRIORITY_MAX (often * %WOCKY_PORTER_HANDLER_PRIORITY_NORMAL). Handlers with a higher priority * (larger number) are called first. - * @callback: A #WockyPorterHandlerFunc, which should return %FALSE to decline - * the stanza (Wocky will continue to the next handler, if any), or %TRUE to - * stop further processing. + * @callback: (scope notified): A #WockyPorterHandlerFunc, which should return + * %FALSE to decline the stanza (Wocky will continue to the next handler, if + * any), or %TRUE to stop further processing. * @user_data: Passed to @callback. * @ap: a wocky_stanza_build() specification. The handler * will match a stanza only if the stanza received is a superset of the one @@ -1641,9 +2187,9 @@ wocky_c2s_porter_register_handler_from_server_va ( * %WOCKY_PORTER_HANDLER_PRIORITY_MAX (often * %WOCKY_PORTER_HANDLER_PRIORITY_NORMAL). Handlers with a higher priority * (larger number) are called first. - * @callback: A #WockyPorterHandlerFunc, which should return %FALSE to decline - * the stanza (Wocky will continue to the next handler, if any), or %TRUE to - * stop further processing. + * @callback: (scope notified): A #WockyPorterHandlerFunc, which should returna + * %FALSE to decline the stanza (Wocky will continue to the next handler, if + * any), or %TRUE to stop further processing. * @user_data: Passed to @callback. * @stanza: a #WockyStanza. The handler will match a stanza only if * the stanza received is a superset of the one passed to this @@ -1688,9 +2234,9 @@ wocky_c2s_porter_register_handler_from_server_by_stanza ( * %WOCKY_PORTER_HANDLER_PRIORITY_MAX (often * %WOCKY_PORTER_HANDLER_PRIORITY_NORMAL). Handlers with a higher priority * (larger number) are called first. - * @callback: A #WockyPorterHandlerFunc, which should return %FALSE to decline - * the stanza (Wocky will continue to the next handler, if any), or %TRUE to - * stop further processing. + * @callback: (scope notified): A #WockyPorterHandlerFunc, which should return + * %FALSE to decline the stanza (Wocky will continue to the next handler, if + * any), or %TRUE to stop further processing. * @user_data: Passed to @callback. * @...: a wocky_stanza_build() specification. The handler * will match a stanza only if the stanza received is a superset of the one @@ -2021,6 +2567,20 @@ wocky_c2s_porter_force_close_async (WockyPorter *porter, priv->force_close_task = NULL; return; } + /* this may be NULL if SM resume/continue has failed */ + if (priv->connection == NULL) + { + GTask *t = priv->force_close_task; + + priv->force_close_task = NULL; + g_clear_object (&priv->force_close_cancellable); + g_clear_object (&priv->receive_cancellable); + priv->local_closed = TRUE; + + g_task_return_boolean (t, TRUE); + g_object_unref (t); + return; + } /* No need to wait, close connection right now */ DEBUG ("remote is already closed, close the XMPP connection"); g_object_ref (self); @@ -2058,11 +2618,17 @@ send_whitespace_ping_cb (GObject *source, WockyC2SPorter *self = WOCKY_C2S_PORTER (g_task_get_source_object (task)); WockyC2SPorterPrivate *priv = self->priv; GError *error = NULL; + gboolean ret; - priv->sending_whitespace_ping = FALSE; + priv->sending_blocked = FALSE; - if (!wocky_xmpp_connection_send_whitespace_ping_finish ( - WOCKY_XMPP_CONNECTION (source), res, &error)) + if (priv->sm && priv->sm->enabled) + ret = wocky_xmpp_connection_send_stanza_finish ( + WOCKY_XMPP_CONNECTION (source), res, &error); + else + ret = wocky_xmpp_connection_send_whitespace_ping_finish ( + WOCKY_XMPP_CONNECTION (source), res, &error); + if (!ret) { g_task_return_error (task, g_error_copy (error)); @@ -2087,6 +2653,33 @@ send_whitespace_ping_cb (GObject *source, g_object_unref (task); } +static void +force_close_conn_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + WockyXmppConnection *conn = WOCKY_XMPP_CONNECTION (source); + GTask *task = G_TASK (user_data); + WockyC2SPorter *self = WOCKY_C2S_PORTER (g_task_get_source_object (task)); + WockyC2SPorterPrivate *priv = wocky_c2s_porter_get_instance_private (self); + GError *error = NULL; + + priv->sending_blocked = FALSE; + + if (wocky_xmpp_connection_force_close_finish (conn, res, &error)) + g_task_return_boolean (task, TRUE); + else + { + DEBUG ("Error closing connection: %s", error->message); + g_task_return_error (task, error); + } + + g_object_unref (task); + + /* reset unacked request number, those are lost */ + priv->sm->reqs = 0; +} + /** * wocky_c2s_porter_send_whitespace_ping_async: * @self: a #WockyC2SPorter @@ -2115,16 +2708,42 @@ wocky_c2s_porter_send_whitespace_ping_async (WockyC2SPorter *self, g_task_return_new_error (task, WOCKY_PORTER_ERROR, WOCKY_PORTER_ERROR_CLOSING, "Porter is closing"); } - else if (sending_in_progress (self)) + else if (sending_in_progress (self) || priv->connection == NULL) { + /* With SM we may have no connection during resumption */ g_task_return_boolean (task, TRUE); + + /* But if connector is set we may need to re-try */ + if (priv->connection == NULL && !priv->sending_blocked) + resume_connection (self); } else { - priv->sending_whitespace_ping = TRUE; + priv->sending_blocked = TRUE; - wocky_xmpp_connection_send_whitespace_ping_async (priv->connection, - cancellable, send_whitespace_ping_cb, g_object_ref (task)); + /* when SM is enabled we need SM ping because we are doing selective + * acks hence need to catch-up the latest acks when idle. */ + if (priv->sm && priv->sm->enabled) + { + if (priv->sm->reqs++ < 2) + { + WockyStanza *r = wocky_stanza_new ("r", WOCKY_XMPP_NS_SM3); + DEBUG ("Ack pressure: %d", priv->sm->reqs); + wocky_xmpp_connection_send_stanza_async (priv->connection, r, + cancellable, send_whitespace_ping_cb, g_object_ref (task)); + g_object_unref (r); + } + else + { + wocky_xmpp_connection_force_close_async (priv->connection, + cancellable, force_close_conn_cb, g_object_ref (task)); + } + } + else + { + wocky_xmpp_connection_send_whitespace_ping_async (priv->connection, + cancellable, send_whitespace_ping_cb, g_object_ref (task)); + } g_signal_emit_by_name (self, "sending", NULL); } diff --git a/wocky/wocky-c2s-porter.h b/wocky/wocky-c2s-porter.h index 83f7638f..88eb1fd3 100644 --- a/wocky/wocky-c2s-porter.h +++ b/wocky/wocky-c2s-porter.h @@ -122,6 +122,23 @@ guint wocky_c2s_porter_register_handler_from_server ( void wocky_c2s_porter_enable_power_saving_mode (WockyC2SPorter *porter, gboolean enable); +typedef struct { + gboolean enabled; /* track sent/rcvd stanzas (not nonzas!) */ + gboolean resumable; /* server confirmed it can resume the stream */ + gchar *id; /* a unique stream identifier for resumption */ + gchar *location; /* preferred server address for resumption */ + gsize timeout; /* a time within which we can try to resume the stream */ + gsize rcv_count; /* a count of stanzas we've received and processed */ + gsize snt_count; /* a count of stanzas we've sent over the wire */ + gsize snt_acked; /* a number of last acked stanzas */ + gint reqs; /* a number of unanswered sm requests */ +} WockyPorterSmCtx; + +const WockyPorterSmCtx * wocky_c2s_porter_get_sm_ctx (WockyC2SPorter *self); + +void wocky_c2s_porter_resume (WockyC2SPorter *self, + WockyXmppConnection *connection); + G_END_DECLS #endif /* #ifndef __WOCKY_C2S_PORTER_H__*/ diff --git a/wocky/wocky-caps-cache.c b/wocky/wocky-caps-cache.c index 5bd7e8dd..3d9838fe 100644 --- a/wocky/wocky-caps-cache.c +++ b/wocky/wocky-caps-cache.c @@ -388,7 +388,7 @@ wocky_caps_cache_new (const gchar *path) * wocky_caps_cache_free_shared() to shared the shared #WockyCapsCache * object. * - * Returns: a new, or cached, #WockyCapsCache. + * Returns: (transfer full): a new, or cached, #WockyCapsCache. */ WockyCapsCache * wocky_caps_cache_dup_shared (void) @@ -571,8 +571,8 @@ caps_cache_touch (WockyCapsCache *self, * Look up @node in the caps cache @self. The caller is responsible * for unreffing the returned #WockyNodeTree. * - * Returns: a #WockyNodeTree if @node was found in the cache, or %NULL - * if a match was not found + * Returns: (transfer full): a #WockyNodeTree if @node was found in the cache, + * or %NULL if a match was not found */ WockyNodeTree * wocky_caps_cache_lookup (WockyCapsCache *self, diff --git a/wocky/wocky-caps-hash.c b/wocky/wocky-caps-hash.c index 01006a7c..c4519bc0 100644 --- a/wocky/wocky-caps-hash.c +++ b/wocky/wocky-caps-hash.c @@ -119,9 +119,11 @@ cmpstringp (const void *p1, /** * wocky_caps_hash_compute_from_lists: - * @features: a #GPtrArray of strings of features - * @identities: a #GPtrArray of #WockyDiscoIdentity structures - * @dataforms: a #GPtrArray of #WockyDataForm objects, or %NULL + * @features: (element-type gchar*): a #GPtrArray of strings of features + * @identities: (element-type WockyDiscoIdentity): a #GPtrArray of + * #WockyDiscoIdentity structures + * @dataforms: (element-type WockyDataForm): a #GPtrArray of #WockyDataForm + * objects, or %NULL * * Compute the hash as defined by the XEP-0115 from a list of * features, identities and dataforms. diff --git a/wocky/wocky-connector.c b/wocky/wocky-connector.c index e313b990..0b6aad05 100644 --- a/wocky/wocky-connector.c +++ b/wocky/wocky-connector.c @@ -73,7 +73,7 @@ * * ① * ↓ - * establish_session ─────────→ success + * establish_session ─────────→ request_sm_enable → complete_operation * ↓ ↑ * establish_session_sent_cb │ * ↓ │ @@ -176,6 +176,8 @@ static void xep77_signup_recv (GObject *source, GAsyncResult *result, gpointer data); +static void request_sm_resume (WockyConnector *self); + static void iq_bind_resource (WockyConnector *self); static void iq_bind_resource_sent_cb (GObject *source, GAsyncResult *result, @@ -293,6 +295,7 @@ struct _WockyConnectorPrivate GSocketConnection *sock; WockyXmppConnection *conn; WockyTLSHandler *tls_handler; + WockyStanza *resume; WockyAuthRegistry *auth_registry; @@ -491,9 +494,13 @@ wocky_connector_set_property (GObject *object, priv->session_id = g_value_dup_string (value); break; case PROP_AUTH_REGISTRY: + if (priv->auth_registry != NULL) + g_object_unref (priv->auth_registry); priv->auth_registry = g_value_dup_object (value); break; case PROP_TLS_HANDLER: + if (priv->tls_handler != NULL) + g_object_unref (priv->tls_handler); priv->tls_handler = g_value_dup_object (value); break; default: @@ -744,7 +751,7 @@ wocky_connector_class_init (WockyConnectorClass *klass) */ spec = g_param_spec_object ("auth-registry", "Authentication Registry", "Authentication Registry", WOCKY_TYPE_AUTH_REGISTRY, - (G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + (G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (oclass, PROP_AUTH_REGISTRY, spec); /** @@ -755,11 +762,12 @@ wocky_connector_class_init (WockyConnectorClass *klass) */ spec = g_param_spec_object ("tls-handler", "TLS Handler", "TLS Handler", WOCKY_TYPE_TLS_HANDLER, - (G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + (G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (oclass, PROP_TLS_HANDLER, spec); /** * WockyConnector::connection-established: + * @connector: a #WockyConnector * @connection: the #GSocketConnection * * Emitted as soon as a connection to the remote server has been @@ -794,6 +802,7 @@ wocky_connector_dispose (GObject *object) g_clear_object (&priv->conn); g_clear_object (&priv->client); g_clear_object (&priv->sock); + g_clear_object (&priv->resume); g_clear_object (&priv->features); g_clear_object (&priv->auth_registry); g_clear_object (&priv->tls_handler); @@ -1312,6 +1321,15 @@ xmpp_features_cb (GObject *source, goto out; } + /* we check for can_bind here as we're supposed to be able to proceed with + * bind should resume fail */ + if (priv->resume && can_bind && wocky_node_get_child_ns (node, "sm", + WOCKY_XMPP_NS_SM3) != NULL) + { + request_sm_resume (self); + goto out; + } + /* we MUST bind here http://www.ietf.org/rfc/rfc3920.txt */ if (can_bind) iq_bind_resource (self); @@ -2024,6 +2042,179 @@ iq_bind_resource_recv_cb (GObject *source, g_object_unref (reply); } +/* ************************************************************************* */ +/* XEP 0198 SM enable/resume calls */ +/* Requesting SM is an opt-in and its failure is non-fatal. + * Therefore we just request it here and handle result in + * porter as part of normal event flow. + */ +static void +request_sm_enable_cb (GObject *source, + GAsyncResult *result, + gpointer data) +{ + WockyXmppConnection *connection = WOCKY_XMPP_CONNECTION (source); + WockyConnector *self = data; + GError *error = NULL; + + if (!wocky_xmpp_connection_send_stanza_finish (connection, result, &error)) + { + DEBUG ("Failed to send enable nonza: %s", error->message); + abort_connect_error (self, &error, "Failed to send 'enable' nonza"); + g_error_free (error); + return; + } + + g_object_set_data_full (G_OBJECT (connection), WOCKY_XMPP_NS_SM3, + g_object_ref (self), g_object_unref); + + complete_operation (self); +} + +static void +request_sm_enable (WockyConnector *self) +{ + WockyConnectorPrivate *priv = wocky_connector_get_instance_private (self); + WockyNode *feat = (priv->features != NULL) ? + wocky_stanza_get_top_node (priv->features) : NULL; + + if ((feat != NULL) && + wocky_node_get_child_ns (feat, "sm", WOCKY_XMPP_NS_SM3)) + { + WockyStanza *enable = wocky_stanza_new ("enable", WOCKY_XMPP_NS_SM3); + WockyNode *en = wocky_stanza_get_top_node (enable); + + wocky_node_set_attributes (en, "max", "600", "resume", "true", NULL); + + wocky_xmpp_connection_send_stanza_async (priv->conn, enable, + priv->cancellable, request_sm_enable_cb, self); + + g_object_unref (enable); + } + else + complete_operation (self); +} + +static void +request_sm_resumed_cb (GObject *source, + GAsyncResult *result, + gpointer data) +{ + WockyXmppConnection *connection = WOCKY_XMPP_CONNECTION (source); + WockyConnector *self = data; + WockyConnectorPrivate *priv = wocky_connector_get_instance_private (self); + const WockyStanza *res; + WockyNode *rn; + GError *error = NULL; + GTask *t = priv->task; + + priv->task = NULL; + g_clear_object (&priv->cancellable); + res = wocky_xmpp_connection_peek_stanza_finish (connection, result, &error); + if (res != NULL) + { + rn = wocky_stanza_get_top_node ((WockyStanza *) res); + if (wocky_node_has_ns (rn, WOCKY_XMPP_NS_SM3)) + { + if (g_strcmp0 (rn->name, "resumed")) + { + error = g_error_new_literal (WOCKY_XMPP_ERROR, + WOCKY_XMPP_ERROR_ITEM_NOT_FOUND, "Resumed session not found"); + } + else + { + g_object_set_data_full (G_OBJECT (connection), WOCKY_XMPP_NS_SM3, + g_object_ref (self), g_object_unref); + } + } + else + { + error = g_error_new_literal (WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_NOT_ACCEPTABLE, + "Connection reuse cannot continue without 'resumed' or 'failed' SM nonza"); + DEBUG ("Cannot continue with '%s': %s", rn->name, error->message); + } + } + else + DEBUG ("Failed to peek resumed nonza: %s", error->message); + + if (error == NULL) + g_task_return_boolean (t, TRUE); + else + g_task_return_error (t, error); + g_object_unref (t); +} + +static void +request_sm_resume_cb (GObject *source, + GAsyncResult *result, + gpointer data) +{ + WockyXmppConnection *connection = WOCKY_XMPP_CONNECTION (source); + WockyConnector *self = data; + WockyConnectorPrivate *priv = wocky_connector_get_instance_private (self); + GError *error = NULL; + + if (!wocky_xmpp_connection_send_stanza_finish (connection, result, &error)) + { + DEBUG ("Failed to send enable nonza: %s", error->message); + abort_connect_error (self, &error, "Failed to send 'resume' nonza"); + g_error_free (error); + return; + } + + wocky_xmpp_connection_peek_stanza_async (connection, priv->cancellable, + request_sm_resumed_cb, self); +} + +static void +request_sm_resume (WockyConnector *self) +{ + WockyConnectorPrivate *priv = wocky_connector_get_instance_private (self); + + wocky_xmpp_connection_send_stanza_async (priv->conn, priv->resume, + priv->cancellable, request_sm_resume_cb, self); + + g_clear_object (&priv->resume); +} + +static void +continue_sm_fail_cb (GObject *source, + GAsyncResult *result, + gpointer data) +{ + WockyXmppConnection *connection = WOCKY_XMPP_CONNECTION (source); + WockyConnector *self = data; + WockyStanza *res; + WockyNode *rn; + GError *error = NULL; + + if ((res = wocky_xmpp_connection_recv_stanza_finish (connection, + result, &error)) == NULL) + { + DEBUG ("Failed to receive SM nonza: %s", error->message); + abort_connect_error (self, &error, "Failed to receive 'failed' SM nonza"); + g_error_free (error); + return; + } + + rn = wocky_stanza_get_top_node ((WockyStanza *) res); + if (wocky_node_has_ns (rn, WOCKY_XMPP_NS_SM3) + && g_strcmp0 (rn->name, "failed") == 0) + { + g_object_unref (res); + /* continue normal connection process */ + iq_bind_resource (self); + return; + } + + g_error_new_literal (WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_NOT_ACCEPTABLE, + "Connection reuse cannot continue without 'failed' SM nonza"); + DEBUG ("Cannot continue with '%s': %s", rn->name, error->message); + abort_connect (self, error); + g_error_free (error); + g_object_unref (res); +} + /* ************************************************************************* */ /* final stage: establish a session, if so advertised: */ void @@ -2067,7 +2258,7 @@ establish_session (WockyConnector *self) priv->cancellable = NULL; } - complete_operation (self); + request_sm_enable (self); } } @@ -2166,7 +2357,7 @@ establish_session_recv_cb (GObject *source, priv->cancellable = NULL; } - complete_operation (self); + request_sm_enable (self); } break; @@ -2208,13 +2399,13 @@ connector_propagate_jid_and_sid (WockyConnector *self, * wocky_connector_connect_finish: * @self: a #WockyConnector instance. * @res: a #GAsyncResult (from your wocky_connector_connect_async() callback). - * @jid: (%NULL to ignore) the user JID from the server is stored here. - * @sid: (%NULL to ignore) the Session ID is stored here. - * @error: (%NULL to ignore) the #GError (if any) is sored here. + * @jid: (nullable): the user JID from the server is stored here. + * @sid: (nullable): the Session ID is stored here. + * @error: (nullable): the #GError (if any) is sored here. * * Called by the callback passed to wocky_connector_connect_async(). * - * Returns: a #WockyXmppConnection instance (success), or %NULL (failure). + * Returns: (transfer full): a #WockyXmppConnection instance (success), or %NULL (failure). */ WockyXmppConnection * wocky_connector_connect_finish (WockyConnector *self, @@ -2223,26 +2414,55 @@ wocky_connector_connect_finish (WockyConnector *self, gchar **sid, GError **error) { + WockyXmppConnection *conn = self->priv->conn; g_return_val_if_fail (g_task_is_valid (res, self), NULL); if (!g_task_propagate_boolean (G_TASK (res), error)) return NULL; connector_propagate_jid_and_sid (self, jid, sid); - return self->priv->conn; + self->priv->conn = NULL; + return conn; +} + +/** + * wocky_connector_resume_finish: + * @self: a #WockyConnector instance. + * @res: a #GAsyncResult as passed to wocky_connector_resume_async() callback. + * @error: (nullable): the #GError (if any) is stored here. + * + * Should be called by the callback passed to wocky_connector_resume_async() + * to complete async operation. + * + * Returns: (transfer full): a #WockyXmppConnection instance (success), or + * %NULL on failure. + */ +WockyXmppConnection * +wocky_connector_resume_finish (WockyConnector *self, + GAsyncResult *res, + GError **error) +{ + WockyXmppConnection *conn = self->priv->conn; + g_return_val_if_fail (g_task_is_valid (res, self), NULL); + + if (!g_task_propagate_boolean (G_TASK (res), error)) + return NULL; + + self->priv->conn = NULL; + return conn; } /** * wocky_connector_register_finish: * @self: a #WockyConnector instance. * @res: a #GAsyncResult (from your wocky_connector_register_async() callback). - * @jid: (%NULL to ignore) the JID in effect after connection is stored here. - * @sid: (%NULL to ignore) the Session ID after connection is stored here. - * @error: (%NULL to ignore) the #GError (if any) is stored here. + * @jid: (nullable): the JID in effect after connection is stored here. + * @sid: (nullable): the Session ID after connection is stored here. + * @error: (nullable): the #GError (if any) is stored here. * * Called by the callback passed to wocky_connector_register_async(). * - * Returns: a #WockyXmppConnection instance (success), or %NULL (failure). + * Returns: (transfer full): a #WockyXmppConnection instance (success), or %NULL (failure). */ WockyXmppConnection * wocky_connector_register_finish (WockyConnector *self, @@ -2264,7 +2484,7 @@ wocky_connector_register_finish (WockyConnector *self, * wocky_connector_unregister_finish: * @self: a #WockyConnector instance. * @res: a #GAsyncResult (from the wocky_connector_unregister_async() callback). - * @error: (%NULL to ignore) the #GError (if any) is stored here. + * @error: (nullable): the #GError (if any) is stored here. * * Called by the callback passed to wocky_connector_unregister_async(). * @@ -2393,6 +2613,81 @@ wocky_connector_connect_async (WockyConnector *self, cancellable, cb, user_data); } +/** + * wocky_connector_resume_async: + * @self: a #WockyConnector instance. + * @resume: (transfer full): a #WockyStanza to send for resumption + * @cancellable: (nullable): a #GCancellable, or %NULL + * @cb: (nullable): a #GAsyncReadyCallback to call when the operation completes. + * @user_data: (nullable): a #gpointer to pass to the callback. + * + * Reconnect to the account/server specified by the @self. + * @cb should invoke wocky_connector_resume_finish(). + */ +void +wocky_connector_resume_async (WockyConnector *self, + WockyStanza *resume, + GCancellable *cancellable, + GAsyncReadyCallback cb, + gpointer user_data) +{ + WockyConnectorPrivate *priv = wocky_connector_get_instance_private (self); + + /* Reset to initial state as we are reusing the connector */ + g_clear_object (&priv->sock); + g_clear_object (&priv->conn); + g_clear_object (&priv->client); + g_clear_object (&priv->resume); + g_clear_object (&priv->features); + g_clear_pointer (&priv->user, g_free); + g_clear_pointer (&priv->domain, g_free); + priv->encrypted = FALSE; + priv->connected = FALSE; + priv->authed = FALSE; + + priv->resume = g_object_ref (resume); + connector_connect_async (self, wocky_connector_resume_async, + cancellable, cb, user_data); +} + +/** + * wocky_connector_continue_async: + * @self: a #WockyConnector instance. + * @cancellable: (nullable): a #GCancellable, or %NULL + * @cb: (nullable): a #GAsyncReadyCallback to call when the operation completes. + * @user_data: (nullable): a #gpointer to pass to the callback. + * + * Continue connector operations (bind+session) on connector instance + * specified by the @self after SM resumption has failed without fatal + * error (item-not-found). + * + * @cb should invoke wocky_connector_connect_finish() - as it would for normal + * connection attempt. + */ +void +wocky_connector_continue_async (WockyConnector *self, + GCancellable *cancellable, + GAsyncReadyCallback cb, + gpointer user_data) +{ + WockyConnectorPrivate *priv = wocky_connector_get_instance_private (self); + + /* Ensure we're in a right state to continue */ + g_assert (priv->task == NULL); + g_assert (priv->cancellable == NULL); + g_assert (priv->conn != NULL); + g_assert (priv->connected); + g_assert (priv->authed); + + priv->task = g_task_new (G_OBJECT (self), cancellable, cb, user_data); + + if (cancellable != NULL) + priv->cancellable = g_object_ref (cancellable); + + /* Fetch `failed` nonza from the xmpp-connection */ + wocky_xmpp_connection_recv_stanza_async (priv->conn, cancellable, + continue_sm_fail_cb, self); +} /** * wocky_connector_unregister_async: @@ -2453,8 +2748,8 @@ wocky_connector_register_async (WockyConnector *self, * Connect to the account/server specified by @self. * To set other #WockyConnector properties, use g_object_new() instead. * - * Returns: a #WockyConnector instance which can be used to connect to, - * register or cancel an account + * Returns: (transfer full): a #WockyConnector instance which can be used to + * connect to, register or cancel an account */ WockyConnector * wocky_connector_new (const gchar *jid, diff --git a/wocky/wocky-connector.h b/wocky/wocky-connector.h index ef543d58..ebea20cf 100644 --- a/wocky/wocky-connector.h +++ b/wocky/wocky-connector.h @@ -198,6 +198,21 @@ gboolean wocky_connector_unregister_finish (WockyConnector *self, void wocky_connector_set_auth_registry (WockyConnector *self, WockyAuthRegistry *registry); +void wocky_connector_continue_async (WockyConnector *self, + GCancellable *cancellable, + GAsyncReadyCallback cb, + gpointer user_data); + +void wocky_connector_resume_async (WockyConnector *self, + WockyStanza *resume, + GCancellable *cancellable, + GAsyncReadyCallback cb, + gpointer user_data); + +WockyXmppConnection *wocky_connector_resume_finish (WockyConnector *self, + GAsyncResult *res, + GError **error); + G_END_DECLS #endif /* #ifndef __WOCKY_CONNECTOR_H__*/ diff --git a/wocky/wocky-contact-factory.c b/wocky/wocky-contact-factory.c index d1ca8ea7..6a5195ca 100644 --- a/wocky/wocky-contact-factory.c +++ b/wocky/wocky-contact-factory.c @@ -259,8 +259,8 @@ wocky_contact_factory_new (void) * is used, but if the contact is not found in the cache, a new * #WockyBareContact is created and cached for future use. * - * Returns: a new reference to a #WockyBareContact instance, which the caller - * is expected to release with g_object_unref() after use. + * Returns: (transfer full): a new reference to a #WockyBareContact instance, + * which the caller is expected to release with g_object_unref() after use. */ WockyBareContact * wocky_contact_factory_ensure_bare_contact (WockyContactFactory *self, @@ -292,9 +292,9 @@ wocky_contact_factory_ensure_bare_contact (WockyContactFactory *self, * Looks up if there's a #WockyBareContact for @bare_jid in the cache, and * returns it if it's found. * - * Returns: a borrowed #WockyBareContact instance (which the caller should - * reference with g_object_ref() if it will be kept), or %NULL if the - * contact is not found. + * Returns: (transfer none): a borrowed #WockyBareContact instance (which the + * caller should reference with g_object_ref() if it will be kept), or %NULL if + * the contact is not found. */ WockyBareContact * wocky_contact_factory_lookup_bare_contact (WockyContactFactory *self, @@ -314,8 +314,8 @@ wocky_contact_factory_lookup_bare_contact (WockyContactFactory *self, * The factory cache is used, but if the resource is not found in the cache, * a new #WockyResourceContact is created and cached for future use. * - * Returns: a new reference to a #WockyResourceContact instance, which the - * caller is expected to release with g_object_unref() after use. + * Returns: (transfer full): a new reference to a #WockyResourceContact instance, + * which the caller is expected to release with g_object_unref() after use. */ WockyResourceContact * wocky_contact_factory_ensure_resource_contact (WockyContactFactory *self, @@ -362,8 +362,8 @@ wocky_contact_factory_ensure_resource_contact (WockyContactFactory *self, * Looks up if there's a #WockyResourceContact for @full_jid in the cache, and * returns it if it's found. * - * Returns: a borrowed #WockyResourceContact instance (which the caller should - * reference with g_object_ref() if it will be kept), or %NULL if the + * Returns: (transfer none): a borrowed #WockyResourceContact instance (which the + * caller should reference with g_object_ref() if it will be kept), or %NULL if the * contact is not found. */ WockyResourceContact * @@ -384,7 +384,7 @@ wocky_contact_factory_lookup_resource_contact (WockyContactFactory *self, * The factory cache is used, but if the contact is not found in the cache, * a new #WockyLLContact is created and cached for future use. * - * Returns: a new reference to a #WockyLLContact instance, which the + * Returns: (transfer full): a new reference to a #WockyLLContact instance, which the * caller is expected to release with g_object_unref() after use. */ WockyLLContact * @@ -419,9 +419,9 @@ wocky_contact_factory_ensure_ll_contact (WockyContactFactory *self, * Looks up if there's a #WockyLLContact for @jid in the cache, and * returns it if it's found. * - * Returns: a borrowed #WockyLLContact instance (which the caller should - * reference with g_object_ref() if it will be kept), or %NULL if the - * contact is not found. + * Returns: (transfer none): a borrowed #WockyLLContact instance (which the + * caller should reference with g_object_ref() if it will be kept), or %NULL if + * the contact is not found. */ WockyLLContact * wocky_contact_factory_lookup_ll_contact (WockyContactFactory *self, @@ -468,12 +468,12 @@ wocky_contact_factory_add_ll_contact (WockyContactFactory *self, /** * wocky_contact_factory_get_ll_contacts: - * @factory: a #WockyContactFactory instance + * @self: a #WockyContactFactory instance * * * - * Returns: a newly allocated #GList of #WockyLLContacts which - * should be freed using g_list_free(). + * Returns: (transfer container)(element-type WockyLLContact): a newly allocated + * #GList of #WockyLLContacts which should be freed using g_list_free(). */ GList * wocky_contact_factory_get_ll_contacts (WockyContactFactory *self) diff --git a/wocky/wocky-contact-factory.h b/wocky/wocky-contact-factory.h index 06eba012..aef65b9b 100644 --- a/wocky/wocky-contact-factory.h +++ b/wocky/wocky-contact-factory.h @@ -100,7 +100,7 @@ WockyLLContact * wocky_contact_factory_lookup_ll_contact ( void wocky_contact_factory_add_ll_contact (WockyContactFactory *factory, WockyLLContact *contact); -GList * wocky_contact_factory_get_ll_contacts (WockyContactFactory *factory); +GList * wocky_contact_factory_get_ll_contacts (WockyContactFactory *self); G_END_DECLS diff --git a/wocky/wocky-disco-identity.c b/wocky/wocky-disco-identity.c index 9da159b2..d3c29274 100644 --- a/wocky/wocky-disco-identity.c +++ b/wocky/wocky-disco-identity.c @@ -115,7 +115,7 @@ wocky_disco_identity_free (WockyDiscoIdentity *identity) * * Creates a new array of #WockyDiscoIdentity structures. * - * Returns: A newly instantiated + * Returns: (transfer full)(element-type WockyDiscoIdentity): A newly instantiated * array. wocky_disco_identity_array_free() should beq used * to free the memory allocated by this array. * See: wocky_disco_identity_array_free() @@ -129,13 +129,13 @@ wocky_disco_identity_array_new (void) /** * wocky_disco_identity_array_copy: - * @source: The source array to be copied. + * @source: (element-type WockyDiscoIdentity): The source array to be copied. * * Copies an array of #WockyDiscoIdentity objects. The returned array contains * new copies of the contents of the source array. * - * Returns: A newly instantiated array with new copies of the contents of the - * source array. + * Returns: (transfer full)(element-type WockyDiscoIdentity): A newly instantiated + * array with new copies of the contents of the source array. * See: wocky_disco_identity_array_new() */ GPtrArray * @@ -158,7 +158,7 @@ wocky_disco_identity_array_copy (const GPtrArray *source) /** * wocky_disco_identity_array_free: - * @arr: Array to be freed. + * @arr: (element-type WockyDiscoIdentity): Array to be freed. * * Frees an array of #WockyDiscoIdentity objects created with * wocky_disco_identity_array_new() or returned by @@ -185,7 +185,7 @@ wocky_disco_identity_array_free (GPtrArray *arr) * * Compares @left and @right. It returns an integer less than, equal * to, or greater than zero if @left is found, respectively, to be - * less than, to match, or be greater than %right. + * less than, to match, or be greater than @right. * * This function can be casted to a %GCompareFunc to sort a list of * #WockyDiscoIdentity structures. diff --git a/wocky/wocky-google-relay.c b/wocky/wocky-google-relay.c index a6a6b425..620ccde8 100644 --- a/wocky/wocky-google-relay.c +++ b/wocky/wocky-google-relay.c @@ -235,6 +235,14 @@ on_http_response (SoupSession *soup, #endif /* ENABLE_GOOGLE_RELAY */ +/** + * wocky_google_relay_resolver_new: (skip) + * + * A constructor for #WockyGoogleRelayResolver + * + * Returns: (transfer full): a new allocated instance of WockyGoogleRelayResolver. + * Release with wocky_google_relay_resolver_destroy() once done. + */ WockyGoogleRelayResolver * wocky_google_relay_resolver_new (void) { @@ -262,6 +270,21 @@ wocky_google_relay_resolver_destroy (WockyGoogleRelayResolver *self) g_slice_free (WockyGoogleRelayResolver, self); } +/** + * wocky_google_relay_resolver_resolve: + * @self: a #WockyGoogleRelayResolver + * @components: a number of info components to get + * @server: Relay server to use + * @port: a port number on relay server + * @token: a relay auth token + * @callback: (scope notified): a #WockyJingleInfoRelaySessionCb to call + * @user_data: a pointer to user data to pass to @cllback + * + * Makes a @components number of soup requests to + * http://@server:@port/create_session, authenticated by @token. After each + * info response is received and parsed executes @callback, passing collected + * and filled relays as #GPtrArray and @user_data. + */ void wocky_google_relay_resolver_resolve (WockyGoogleRelayResolver *self, guint components, diff --git a/wocky/wocky-google-relay.h b/wocky/wocky-google-relay.h index 0e3276f1..d85707c3 100644 --- a/wocky/wocky-google-relay.h +++ b/wocky/wocky-google-relay.h @@ -36,7 +36,7 @@ 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, + guint components, const gchar *server, guint16 port, const gchar *token, diff --git a/wocky/wocky-jabber-auth.h b/wocky/wocky-jabber-auth.h index d8181c0a..2eb7b0df 100644 --- a/wocky/wocky-jabber-auth.h +++ b/wocky/wocky-jabber-auth.h @@ -91,10 +91,6 @@ gboolean wocky_jabber_auth_authenticate_finish (WockyJabberAuth *self, GAsyncResult *result, GError **error); -void -wocky_jabber_auth_add_handler (WockyJabberAuth *auth, - WockyAuthHandler *handler); - G_END_DECLS #endif /* #ifndef __WOCKY_JABBER_AUTH_H__*/ diff --git a/wocky/wocky-jingle-content.c b/wocky/wocky-jingle-content.c index e941df94..c8e78ea3 100644 --- a/wocky/wocky-jingle-content.c +++ b/wocky/wocky-jingle-content.c @@ -281,6 +281,7 @@ wocky_jingle_content_class_init (WockyJingleContentClass *cls) 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, @@ -354,6 +355,14 @@ wocky_jingle_content_class_init (WockyJingleContentClass *cls) NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); + /** + * WockyJingleContent::new-share-channel: + * @content: the content + * @name: new channel name + * @id: new channel content id + * + * Emitted when new channel is created. + */ signals[NEW_SHARE_CHANNEL] = g_signal_new ( "new-share-channel", G_TYPE_FROM_CLASS (cls), @@ -375,7 +384,10 @@ wocky_jingle_content_class_init (WockyJingleContentClass *cls) G_TYPE_NONE, 0); - /* This signal serves as notification that the WockyJingleContent is now + /** + * WockyJingleContent::removed: + * + * This signal serves as notification that the WockyJingleContent is now * meaningless; everything holding a reference should drop it after receiving * 'removed'. */ @@ -457,7 +469,6 @@ transport_created (WockyJingleContent *c) virtual_method (c, c->priv->transport); } - static void parse_description (WockyJingleContent *c, WockyNode *desc_node, GError **error) @@ -1116,6 +1127,15 @@ wocky_jingle_content_set_transport_state (WockyJingleContent *self, _maybe_ready (self); } +/** + * wocky_jingle_content_get_remote_candidates: + * @c: a #WockyJingleContent + * + * Obtains a pointer to the list of candidates from the transport of the @c + * + * Returns: (transfer none)(element-type WockyJingleCandidate): a pointer to + * transport internal #GList with #WockyJingleCandidate + */ GList * wocky_jingle_content_get_remote_candidates (WockyJingleContent *c) { @@ -1124,6 +1144,15 @@ wocky_jingle_content_get_remote_candidates (WockyJingleContent *c) return wocky_jingle_transport_iface_get_remote_candidates (priv->transport); } +/** + * wocky_jingle_content_get_local_candidates: + * @c: a #WockyJingleContent + * + * Obtains a pointer to the list of candidates from the transport of the @c + * + * Returns: (transfer none)(element-type WockyJingleCandidate): a pointer to + * transport internal #GList with #WockyJingleCandidate + */ GList * wocky_jingle_content_get_local_candidates (WockyJingleContent *c) { diff --git a/wocky/wocky-jingle-content.h b/wocky/wocky-jingle-content.h index e1617433..00e03466 100644 --- a/wocky/wocky-jingle-content.h +++ b/wocky/wocky-jingle-content.h @@ -45,6 +45,22 @@ typedef enum { WOCKY_JINGLE_CONTENT_STATE_REMOVING } WockyJingleContentState; +/** + * WockyJingleCandidate: + * @protocol: network transport protocol: tcp or udp + * @type: a type of the candidate: local, stun or relay + * @id: candidate id + * @address: candidate address + * @port: candidate port number + * @component: jingle candidate component + * @generation: candidate generation + * @preference: candidate preference + * @username: (nullable): optional username for candidate + * @password: (nullable): optional password for candidate + * @network: candidate network + * + * A wocky wrapper for jingle candidate. + */ struct _WockyJingleCandidate { WockyJingleTransportProtocol protocol; WockyJingleCandidateType type; @@ -85,12 +101,12 @@ GType wocky_jingle_content_get_type (void); 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 *); + void (*parse_description) (WockyJingleContent *c, WockyNode *desc_node, + GError **error); + void (*produce_description) (WockyJingleContent *c, WockyNode *desc_node); + void (*transport_created) (WockyJingleContent *c, + WockyJingleTransportIface *t); + WockyJingleContentSenders (*get_default_senders) (WockyJingleContent *c); }; typedef struct _WockyJingleContentPrivate WockyJingleContentPrivate; diff --git a/wocky/wocky-jingle-factory.c b/wocky/wocky-jingle-factory.c index 8bef2bbd..ef6ce12d 100644 --- a/wocky/wocky-jingle-factory.c +++ b/wocky/wocky-jingle-factory.c @@ -621,6 +621,14 @@ session_terminated_cb (WockyJingleSession *session, g_free (key); } +/** + * wocky_jingle_factory_get_jingle_info: + * @self: a #WockyJingleFactory + * + * Obtains #WockyJingleInfo of the current factory. + * + * Returns: (transfer none): a pointer to internal #WockyJingleInfo object. + */ WockyJingleInfo * wocky_jingle_factory_get_jingle_info ( WockyJingleFactory *self) diff --git a/wocky/wocky-jingle-factory.h b/wocky/wocky-jingle-factory.h index 7d874894..0b796ad7 100644 --- a/wocky/wocky-jingle-factory.h +++ b/wocky/wocky-jingle-factory.h @@ -85,7 +85,7 @@ WockyJingleSession *wocky_jingle_factory_create_session ( gboolean local_hold); WockyJingleInfo *wocky_jingle_factory_get_jingle_info ( - WockyJingleFactory *fac); + WockyJingleFactory *self); G_END_DECLS diff --git a/wocky/wocky-jingle-info.c b/wocky/wocky-jingle-info.c index 9c57034e..cd9f80ed 100644 --- a/wocky/wocky-jingle-info.c +++ b/wocky/wocky-jingle-info.c @@ -650,12 +650,13 @@ wocky_jingle_info_send_request ( wocky_jingle_info_lookup_srv (self); } -/* +/** * wocky_jingle_info_get_stun_servers: + * @self: a #WockyJingleInfo * * Grabs the currently known and resolved stun servers. * - * Returns: (transfer container): a list of WockyJingleInfo structs + * Returns: (transfer container)(element-type WockyJingleInfo): a list of WockyJingleInfo structs */ GList * wocky_jingle_info_get_stun_servers ( @@ -682,6 +683,45 @@ wocky_jingle_info_get_google_relay_token ( return self->priv->relay_token; } +static WockyJingleRelay * +wocky_jingle_relay_copy (WockyJingleRelay *from) +{ + WockyJingleRelay *to = g_slice_dup (WockyJingleRelay, from); + + to->username = g_strdup (to->username); + to->password = g_strdup (to->password); + to->ip = g_strdup (to->ip); + + return to; +} + +GType +wocky_jingle_relay_get_type (void) +{ + static GType t = 0; + + if (G_UNLIKELY (t == 0)) + t = g_boxed_type_register_static ("WockyJingleRelay", + (GBoxedCopyFunc) wocky_jingle_relay_copy, + (GBoxedFreeFunc) wocky_jingle_relay_free); + + return t; +} + +/** + * wocky_jingle_relay_new: + * @type: a #WockyJingleRelayType to create + * @ip: jingle relay's ip address + * @port: jingle relay's port number + * @username: relay auth username + * @password: relay auth password + * @component: a jingle component + * + * Creates #WockyJingleRelay struct with input parameters populated. + * + * Returns: a newly allocated and initialized #WockyJingleRelay. Use + * wocky_jingle_relay_free() to free the struct once done. + */ WockyJingleRelay * wocky_jingle_relay_new ( WockyJingleRelayType type, @@ -706,6 +746,15 @@ wocky_jingle_relay_free (WockyJingleRelay *relay) g_slice_free (WockyJingleRelay, relay); } +/** + * wocky_jingle_info_create_google_relay_session: + * @self: a #WockyJingleInfo + * @components: a number of relay components + * @callback: (scope async): a #WockyJingleInfoRelaySessionCb to call on session + * @user_data: a data to pass to @callback + * + * Creates new Google Relay Jingle session on @self. + */ void wocky_jingle_info_create_google_relay_session ( WockyJingleInfo *self, diff --git a/wocky/wocky-jingle-info.h b/wocky/wocky-jingle-info.h index 41d2e21e..f598a231 100644 --- a/wocky/wocky-jingle-info.h +++ b/wocky/wocky-jingle-info.h @@ -90,6 +90,10 @@ WockyJingleRelay *wocky_jingle_relay_new ( guint component); void wocky_jingle_relay_free (WockyJingleRelay *relay); +#define WOCKY_TYPE_JINGLE_RELAY \ + (wocky_jingle_relay_get_type ()) +GType wocky_jingle_relay_get_type (void); + /* * @relays: (element-type WockyJingleRelay) (transfer none): a possibly-empty * array of WockyJingleRelay structs. diff --git a/wocky/wocky-jingle-media-rtp.c b/wocky/wocky-jingle-media-rtp.c index fddc4275..86ad765c 100644 --- a/wocky/wocky-jingle-media-rtp.c +++ b/wocky/wocky-jingle-media-rtp.c @@ -197,6 +197,15 @@ build_codec_table (GList *codecs) return table; } +/** + * jingle_media_rtp_copy_codecs: + * @codecs: (element-type WockyJingleCodec): a #GList of #WockyJingleCodec structs + * + * Performs deep copy of the list by copying list and its content. + * + * Returns: (transfer full)(element-type WockyJingleCodec): a new #GList which + * is a deep copy of the @codecs. + */ GList * jingle_media_rtp_copy_codecs (GList *codecs) { @@ -214,6 +223,12 @@ jingle_media_rtp_copy_codecs (GList *codecs) return ret; } +/** + * jingle_media_rtp_free_codecs: + * @codecs: (element-type WockyJingleCodec): a #GList of #WockyJingleCodec structs + * + * Frees the list of codecs and its content. + */ void jingle_media_rtp_free_codecs (GList *codecs) { @@ -1178,11 +1193,13 @@ string_string_maps_equal (GHashTable *a, } /** - * 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 + * jingle_media_rtp_compare_codecs: + * @old: (element-type WockyJingleCodec): previous local codecs + * @new: (element-type WockyJingleCodec): new local codecs supplied by streaming + * implementation + * @changed: (out)(element-type WockyJingleCodec): location at which to store + * the changed codecs + * @e: (out): location at which to store an error if the update was invalid * * Returns: %TRUE if the update made sense, %FALSE with @error set otherwise */ @@ -1333,7 +1350,7 @@ jingle_media_rtp_register (WockyJingleFactory *factory) * @self : the RTP content * * Gets the current remote media description, if known. The - * #WockyJingleMediaRtp:remote-media-description signal is emitted when this + * #WockyJingleMediaRtp::remote-media-description signal is emitted when this * value changes. * * Returns: (transfer none): the current remote media description, which may be diff --git a/wocky/wocky-jingle-session.c b/wocky/wocky-jingle-session.c index 99a037f8..79a67f7b 100644 --- a/wocky/wocky-jingle-session.c +++ b/wocky/wocky-jingle-session.c @@ -1745,6 +1745,18 @@ wocky_jingle_session_parse ( return wocky_jingle_state_machine_dance (sess, action, session_node, error); } +/** + * wocky_jingle_session_new_message: + * @sess: a WockyJingleSession + * @action: a #WockyJingleAction + * @sess_node: (out)(optional): a pointer to #WockyNode + * + * Creates a new IQ stanza for the current session, containing specified + * @action, and sets @sess_node to corresponding session XML node. + * + * Returns: (transfer full): a new #WockyStanza with session @action and + * sets @sess_node pointer to session XML node, unless it is %NULL. + */ WockyStanza * wocky_jingle_session_new_message (WockyJingleSession *sess, WockyJingleAction action, WockyNode **sess_node) @@ -2406,10 +2418,18 @@ _get_any_content (WockyJingleSession *session) return c; } -/* Note: if there are multiple content types, not guaranteed which one will +/** + * wocky_jingle_session_get_content_type: + * @sess: a #WockyJingleSession + * + * 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. */ + * should not be a problem. + * + * Returns: #GType of the #WockyJingleContent for this session, 0 if there + * are no contents yet. + */ GType wocky_jingle_session_get_content_type (WockyJingleSession *sess) { @@ -2426,6 +2446,16 @@ wocky_jingle_session_get_content_type (WockyJingleSession *sess) } /* FIXME: probably should make this into a property */ +/** + * wocky_jingle_session_get_contents: + * @sess: a #WockyJingleSession + * + * Obtains a list of @self's content fragments, initiator's followed by + * responder's + * + * Returns: (transfer full)(element-type WockyJingleContent): a new #GList + * with @self's #WockyJingleContent fragments + */ GList * wocky_jingle_session_get_contents (WockyJingleSession *sess) { @@ -2553,6 +2583,14 @@ wocky_jingle_session_get_dialect (WockyJingleSession *sess) return sess->priv->dialect; } +/** + * wocky_jingle_session_get_peer_contact: + * @self: a #WockyJingleSession + * + * Obtains a pointer to @self's peer_contact + * + * Returns: (transfer none): a pointer to @self's #WockyContact for the peer + */ WockyContact * wocky_jingle_session_get_peer_contact (WockyJingleSession *self) { @@ -2575,6 +2613,14 @@ wocky_jingle_session_get_peer_jid (WockyJingleSession *sess) return sess->priv->peer_jid; } +/** + * wocky_jingle_session_get_factory: + * @self: a #WockyJingleSession + * + * Obtains a pointer to @self's factory + * + * Returns: (transfer none): a pointer to @self's #WockyJingleFactory + */ WockyJingleFactory * wocky_jingle_session_get_factory (WockyJingleSession *self) { @@ -2583,6 +2629,14 @@ wocky_jingle_session_get_factory (WockyJingleSession *self) return self->priv->jingle_factory; } +/** + * wocky_jingle_session_get_porter: + * @self: a #WockyJingleSession + * + * Obtains a pointer to @self's porter + * + * Returns: (transfer none): a pointer to @self's #WockyPorter + */ WockyPorter * wocky_jingle_session_get_porter (WockyJingleSession *self) { diff --git a/wocky/wocky-jingle-session.h b/wocky/wocky-jingle-session.h index 43c8db33..d359a54b 100644 --- a/wocky/wocky-jingle-session.h +++ b/wocky/wocky-jingle-session.h @@ -95,7 +95,7 @@ wocky_jingle_session_add_content (WockyJingleSession *sess, const gchar *content_ns, const gchar *transport_ns); -GType wocky_jingle_session_get_content_type (WockyJingleSession *); +GType wocky_jingle_session_get_content_type (WockyJingleSession *sess); GList *wocky_jingle_session_get_contents (WockyJingleSession *sess); const gchar *wocky_jingle_session_get_peer_resource ( WockyJingleSession *sess); diff --git a/wocky/wocky-jingle-transport-iface.c b/wocky/wocky-jingle-transport-iface.c index 06e38152..97e29654 100644 --- a/wocky/wocky-jingle-transport-iface.c +++ b/wocky/wocky-jingle-transport-iface.c @@ -25,6 +25,17 @@ #include "wocky-jingle-content.h" #include "wocky-jingle-session.h" +/** + * wocky_jingle_transport_iface_new: + * @type: a #GType with actual object type implementing WockyJingleTransportIface + * interface + * @content: a #WockyJingleContent to transfer by transport + * @transport_ns: the XML namespace representing Jingle Transport + * + * Creates a new object of type @type which should implement this interface. + * + * Returns: (transfer full): a new Jingle Transport object of type @type + */ WockyJingleTransportIface * wocky_jingle_transport_iface_new (GType type, WockyJingleContent *content, @@ -51,7 +62,13 @@ wocky_jingle_transport_iface_parse_candidates (WockyJingleTransportIface *self, return virtual_method (self, node, error); } -/* Takes in a list of slice-allocated WockyJingleCandidate structs */ +/** + * wocky_jingle_transport_iface_new_local_candidates: + * @self: a #WockyJingleTransportIface instance + * @candidates: (element-type WockyJingleCandidate): a #GList of + * + * Takes in a list of slice-allocated WockyJingleCandidate structs + */ void wocky_jingle_transport_iface_new_local_candidates (WockyJingleTransportIface *self, GList *candidates) @@ -114,6 +131,16 @@ wocky_jingle_transport_iface_can_accept (WockyJingleTransportIface *self) return TRUE; } +/** + * wocky_jingle_transport_iface_get_remote_candidates: + * @self: a #WockyJingleTransportIface instance + * + * Obtains a pointer to internal list of #WockyJingleCandidate structs with + * remote candidates + * + * Returns: (transfer none)(element-type WockyJingleCandidate): a pointer to + * internal #GList of #WockyJingleCandidate structs + */ GList * wocky_jingle_transport_iface_get_remote_candidates ( WockyJingleTransportIface *self) @@ -125,6 +152,16 @@ wocky_jingle_transport_iface_get_remote_candidates ( return virtual_method (self); } +/** + * wocky_jingle_transport_iface_get_local_candidates: + * @self: a #WockyJingleTransportIface instance + * + * Obtains a pointer to internal list of #WockyJingleCandidate structs with + * local condidates + * + * Returns: (transfer none)(element-type WockyJingleCandidate): a pointer to + * internal #GList of #WockyJingleCandidate structs + */ GList * wocky_jingle_transport_iface_get_local_candidates ( WockyJingleTransportIface *self) @@ -281,6 +318,13 @@ wocky_jingle_candidate_free (WockyJingleCandidate *c) g_slice_free (WockyJingleCandidate, c); } +/** + * jingle_transport_free_candidates: + * @candidates: (element-type WockyJingleCandidate): A #GList to deep-free + * + * Performs a free operation on #GList and its content, calling + * wocky_jingle_candidate_free() for each #WockyJingleCandidate member. + */ void jingle_transport_free_candidates (GList *candidates) { diff --git a/wocky/wocky-jingle-transport-iface.h b/wocky/wocky-jingle-transport-iface.h index 99e1a1ac..56a76df0 100644 --- a/wocky/wocky-jingle-transport-iface.h +++ b/wocky/wocky-jingle-transport-iface.h @@ -43,18 +43,18 @@ typedef struct _WockyJingleTransportIfaceClass WockyJingleTransportIfaceClass; struct _WockyJingleTransportIfaceClass { GTypeInterface parent; - void (*parse_candidates) (WockyJingleTransportIface *, - WockyNode *, GError **); + void (*parse_candidates) (WockyJingleTransportIface *self, + WockyNode *node, GError **error); - void (*new_local_candidates) (WockyJingleTransportIface *, GList *); - void (*inject_candidates) (WockyJingleTransportIface *, + void (*new_local_candidates) (WockyJingleTransportIface *self, GList *candidates); + void (*inject_candidates) (WockyJingleTransportIface *self, WockyNode *transport_node); - void (*send_candidates) (WockyJingleTransportIface *, gboolean all); - gboolean (*can_accept) (WockyJingleTransportIface *); + void (*send_candidates) (WockyJingleTransportIface *self, gboolean all); + gboolean (*can_accept) (WockyJingleTransportIface *self); - GList * (*get_remote_candidates) (WockyJingleTransportIface *); - GList * (*get_local_candidates) (WockyJingleTransportIface *); - gboolean (*get_credentials) (WockyJingleTransportIface *, + GList * (*get_remote_candidates) (WockyJingleTransportIface *self); + GList * (*get_local_candidates) (WockyJingleTransportIface *self); + gboolean (*get_credentials) (WockyJingleTransportIface *self, gchar **ufrag, gchar **pwd); WockyJingleTransportType (*get_transport_type) (void); @@ -73,8 +73,8 @@ GType wocky_jingle_transport_iface_get_type (void); (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_parse_candidates (WockyJingleTransportIface *self, + WockyNode *node, GError **error); void wocky_jingle_transport_iface_new_local_candidates ( WockyJingleTransportIface *self, @@ -88,10 +88,10 @@ void wocky_jingle_transport_iface_send_candidates ( 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 *, +GList *wocky_jingle_transport_iface_get_remote_candidates (WockyJingleTransportIface *self); +GList *wocky_jingle_transport_iface_get_local_candidates (WockyJingleTransportIface *self); +WockyJingleTransportType wocky_jingle_transport_iface_get_transport_type (WockyJingleTransportIface *self); +gboolean jingle_transport_get_credentials (WockyJingleTransportIface *self, gchar **ufrag, gchar **pwd); WockyJingleTransportIface *wocky_jingle_transport_iface_new ( diff --git a/wocky/wocky-jingle-types.h b/wocky/wocky-jingle-types.h index 93783713..759642d1 100644 --- a/wocky/wocky-jingle-types.h +++ b/wocky/wocky-jingle-types.h @@ -23,6 +23,7 @@ #ifndef __WOCKY_JINGLE_TYPES_H__ #define __WOCKY_JINGLE_TYPES_H__ +#ifndef __GTK_DOC_IGNORE__ typedef struct _WockyJingleFactory WockyJingleFactory; typedef struct _WockyJingleSession WockyJingleSession; typedef struct _WockyJingleContent WockyJingleContent; @@ -31,17 +32,25 @@ typedef struct _WockyJingleTransportRawUdp WockyJingleTransportRawUdp; typedef struct _WockyJingleTransportIceUdp WockyJingleTransportIceUdp; typedef struct _WockyJingleMediaRtp WockyJingleMediaRtp; typedef struct _WockyJingleCandidate WockyJingleCandidate; +#endif /* __GTK_DOC_IGNORE__ */ -typedef enum { /*< skip >*/ - /* not a jingle message */ +/** + * WockyJingleDialect: + * @WOCKY_JINGLE_DIALECT_ERROR: not a jingle message + * @WOCKY_JINGLE_DIALECT_GTALK3: relict libjingle3 gtalk variant + * @WOCKY_JINGLE_DIALECT_GTALK4: legacy and latest gtalk variant + * @WOCKY_JINGLE_DIALECT_V015: relict jingle version 0.15 + * @WOCKY_JINGLE_DIALECT_V032: old jingle version 0.32 + * + * The type of jingle session dialect. Currently supports outdated dialects. + */ +typedef enum { 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; @@ -65,7 +74,7 @@ typedef enum { /*< skip >*/ * * Possible states of a #WockyJingleSession. */ -typedef enum { /*< skip >*/ +typedef enum { /*< private >*/ WOCKY_JINGLE_STATE_INVALID = -1, /*< public >*/ @@ -79,7 +88,57 @@ typedef enum { /*< skip >*/ WOCKY_N_JINGLE_STATES } WockyJingleState; -typedef enum { /*< skip >*/ +/** + * WockyJingleAction: + * @WOCKY_JINGLE_ACTION_UNKNOWN: Unknown/undefined action type + * @WOCKY_JINGLE_ACTION_CONTENT_ACCEPT: + * action="content-accept" + * @WOCKY_JINGLE_ACTION_CONTENT_ADD: + * action="content-add" + * @WOCKY_JINGLE_ACTION_CONTENT_MODIFY: + * action="content-modify" + * @WOCKY_JINGLE_ACTION_CONTENT_REMOVE: + * action="content-remove" + * @WOCKY_JINGLE_ACTION_CONTENT_REPLACE: deprecated + * @WOCKY_JINGLE_ACTION_CONTENT_REJECT: + * action="content-reject" + * @WOCKY_JINGLE_ACTION_SESSION_ACCEPT: + * action="session-accept" + * @WOCKY_JINGLE_ACTION_SESSION_INFO: + * action="security-info" + * @WOCKY_JINGLE_ACTION_SESSION_INITIATE: + * action="session-initiate" + * @WOCKY_JINGLE_ACTION_SESSION_TERMINATE: + * action="session-terminate" + * @WOCKY_JINGLE_ACTION_TRANSPORT_INFO: + * action="transport-info" + * @WOCKY_JINGLE_ACTION_TRANSPORT_ACCEPT: + * action="transport-accept" + * @WOCKY_JINGLE_ACTION_DESCRIPTION_INFO: + * action="description-info" + * @WOCKY_JINGLE_ACTION_INFO: deprecated + * + * The Jingle action as per XEP-0166 Jingle + * §7.2 specification. + * Missing Actions (introduced in v0.35 with ns 'urn:xmpp:jingle:1'): + * * https://xmpp.org/extensions/xep-0166.html#def-action-security-info + * * https://xmpp.org/extensions/xep-0166.html#def-action-transport-replace + * * https://xmpp.org/extensions/xep-0166.html#def-action-transport-reject + */ +typedef enum { WOCKY_JINGLE_ACTION_UNKNOWN, WOCKY_JINGLE_ACTION_CONTENT_ACCEPT, WOCKY_JINGLE_ACTION_CONTENT_ADD, @@ -116,7 +175,13 @@ typedef enum { /*< skip >*/ WOCKY_JINGLE_TRANSPORT_PROTOCOL_TCP } WockyJingleTransportProtocol; -typedef enum { /*< skip >*/ +/** + * WockyJingleCandidateType: + * @WOCKY_JINGLE_CANDIDATE_TYPE_LOCAL: local candidate (direct endpoint) + * @WOCKY_JINGLE_CANDIDATE_TYPE_STUN: candidate is mediated via STUN + * @WOCKY_JINGLE_CANDIDATE_TYPE_RELAY: candidate is mediated via TURN + */ +typedef enum { WOCKY_JINGLE_CANDIDATE_TYPE_LOCAL, WOCKY_JINGLE_CANDIDATE_TYPE_STUN, WOCKY_JINGLE_CANDIDATE_TYPE_RELAY @@ -125,6 +190,23 @@ typedef enum { /*< skip >*/ /** * WockyJingleReason: * @WOCKY_JINGLE_REASON_UNKNOWN: no known reason + * @WOCKY_JINGLE_REASON_ALTERNATIVE_SESSION: <alternative-session/> + * @WOCKY_JINGLE_REASON_BUSY: <busy/> + * @WOCKY_JINGLE_REASON_CANCEL: <cancel/> + * @WOCKY_JINGLE_REASON_CONNECTIVITY_ERROR: <connectivity-error/> + * @WOCKY_JINGLE_REASON_DECLINE: <decline/> + * @WOCKY_JINGLE_REASON_EXPIRED: <expired/> + * @WOCKY_JINGLE_REASON_FAILED_APPLICATION: <failed-application/> + * @WOCKY_JINGLE_REASON_FAILED_TRANSPORT: <failed-transport/> + * @WOCKY_JINGLE_REASON_GENERAL_ERROR: <general-error/> + * @WOCKY_JINGLE_REASON_GONE: <gone/> + * @WOCKY_JINGLE_REASON_INCOMPATIBLE_PARAMETERS: <incompatible-parameters/> + * @WOCKY_JINGLE_REASON_MEDIA_ERROR: <media-error/> + * @WOCKY_JINGLE_REASON_SECURITY_ERROR: <security-error/> + * @WOCKY_JINGLE_REASON_SUCCESS: <success/> + * @WOCKY_JINGLE_REASON_TIMEOUT: <timeout/> + * @WOCKY_JINGLE_REASON_UNSUPPORTED_APPLICATIONS: <unsupported-applications/> + * @WOCKY_JINGLE_REASON_UNSUPPORTED_TRANSPORTS:<unsupported-transports/> * * The reason for a Jingle action occurring—specifically, the reason for * terminating a call. See user; } +/** + * wocky_muc_members: + * @muc: a #WockyMuc + * + * Get internal list of @muc's members + * + * Returns: (transfer full)(nullable): a reference to internal GHashTable with + * members, or %NULL if there're none. Once done unref with + * g_hash_table_unref(). + */ GHashTable * wocky_muc_members (WockyMuc *muc) { diff --git a/wocky/wocky-namespaces.h b/wocky/wocky-namespaces.h index 758cffb7..d076e9d0 100644 --- a/wocky/wocky-namespaces.h +++ b/wocky/wocky-namespaces.h @@ -26,6 +26,9 @@ #define WOCKY_XMPP_NS_SASL_AUTH \ "urn:ietf:params:xml:ns:xmpp-sasl" +#define WOCKY_XMPP_NS_SM3 \ + "urn:xmpp:sm:3" + #define WOCKY_NS_DISCO_INFO \ "http://jabber.org/protocol/disco#info" diff --git a/wocky/wocky-node-tree.c b/wocky/wocky-node-tree.c index 4ea0431c..2e54805c 100644 --- a/wocky/wocky-node-tree.c +++ b/wocky/wocky-node-tree.c @@ -205,6 +205,14 @@ wocky_node_tree_new_from_node (WockyNode *node) return g_object_new (WOCKY_TYPE_NODE_TREE, "top-node", top, NULL); } +/** + * wocky_node_tree_get_top_node: + * @self: a #WockyNodeTree + * + * Retrieves root element of the @self which represents top-most element. + * + * Returns: (transfer none): a pointer to the root of the #WockyNodeTree object. + */ WockyNode * wocky_node_tree_get_top_node (WockyNodeTree *self) { diff --git a/wocky/wocky-pep-service.c b/wocky/wocky-pep-service.c index eca31b4c..65c99066 100644 --- a/wocky/wocky-pep-service.c +++ b/wocky/wocky-pep-service.c @@ -417,7 +417,7 @@ wocky_pep_service_get_async (WockyPepService *self, * #WockyPepService:node. For more details, see * wocky_pep_service_get_async(). * - * Returns: the #WockyStanza retrieved from getting the PEP node. + * Returns: (transfer full): the #WockyStanza retrieved from getting the PEP node. */ WockyStanza * wocky_pep_service_get_finish (WockyPepService *self, @@ -455,7 +455,8 @@ wocky_pep_service_get_finish (WockyPepService *self, * * Generates a new IQ type='set' PEP publish stanza. * - * Returns: a new #WockyStanza PEP publish stanza; free with g_object_unref() + * Returns: (transfer full): a new #WockyStanza PEP publish stanza; + * free with g_object_unref() */ WockyStanza * wocky_pep_service_make_publish_stanza (WockyPepService *self, diff --git a/wocky/wocky-ping.h b/wocky/wocky-ping.h index c0c4bb8c..169b081a 100644 --- a/wocky/wocky-ping.h +++ b/wocky/wocky-ping.h @@ -41,8 +41,6 @@ typedef struct _WockyPing WockyPing; typedef struct _WockyPingClass WockyPingClass; typedef struct _WockyPingPrivate WockyPingPrivate; -GQuark wocky_ping_error_quark (void); - struct _WockyPingClass { /**/ GObjectClass parent_class; diff --git a/wocky/wocky-porter.c b/wocky/wocky-porter.c index 4161fe25..6ab67c84 100644 --- a/wocky/wocky-porter.c +++ b/wocky/wocky-porter.c @@ -133,6 +133,86 @@ wocky_porter_default_init (WockyPorterInterface *iface) g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, WOCKY_TYPE_STANZA); + /** + * WockyPorter::resuming: + * @porter: the object on which the signal is emitted + * @nonza: the resume #WockyStanza to send for resumption + * + * The ::resuming signal is emitted when the #WockyPorter detects broken + * XMPP connection and start resumption vector of the attached connector. + * After this signal all outgoing stanzas will be queued and might be + * discarded if XEP-0198 resumption fails. The signal is emitted after + * XMPP connection is discarded from the porter (which also sends + * notify::connection for changed property) but before connector_resume. + * The signal passes resume stanza from the porter which needs to be + * passed to the wocky_connector_resume_async call should signal handler + * decide to take over the resumption control flow by returning FALSE. + */ + g_signal_new ("resuming", iface_type, + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + NULL, G_TYPE_BOOLEAN, 1, WOCKY_TYPE_STANZA); + + /** + * WockyPorter::resumed: + * @porter: the object on which the signal is emitted + * + * The ::resumed signal is emitted when the #WockyPorter resumed the + * XMPP connection, returned from connector and processed XEP-0198 + * `resumed` nonza. This signal may be used to update UI that connection + * is fully available for send/receive now. + */ + g_signal_new ("resumed", iface_type, + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * WockyPorter::resume-done: + * @porter: the object on which the signal is emitted + * + * The ::resume-done signal is emitted when the #WockyPorter finished + * flushing sending queues after XMPP connection is resumed. + * This signal may be used to reset sending timeouts. + */ + g_signal_new ("resume-done", iface_type, + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * WockyPorter::resume-failed: + * @porter: the object on which the signal is emitted + * + * The ::resume-failed signal is emitted when the #WockyPorter returns + * from connector with soft-error - the connection is established but + * the session is not found on the server. As per XEP-0198 the client + * may proceed with bind at this stage. If signal returns with %FALSE + * the porter simply returns from this error without action. You would + * need to call wocky_connector_reconnect_async manually to proceed with + * the bind (and obtain XMPP connection). If signal returns with %TRUE + * then #WockyPorter calls the _reconnect call and proceeds with bind + * automatically. + * Note: In case of hard-fail #WockyPorter will continue trying to + * reconnect with each heartbeat. + */ + g_signal_new ("resume-failed", iface_type, + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + NULL, G_TYPE_BOOLEAN, 0); + + /** + * WockyPorter::reconnected: + * @porter: the object on which the signal is emitted + * @sid: a new SM sid for newly created session + * @jid: a new JID bound to the newly created session + * + * The ::reconnected signal is emitted when the #WockyPorter completes + * automatic reconnection after resumption soft-fail. The signal carries + * new SID and JID of the re-bound session. + */ + g_signal_new ("reconnected", iface_type, + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); + g_once_init_leave (&initialization_value, 1); } } @@ -329,9 +409,9 @@ wocky_porter_send (WockyPorter *porter, * %WOCKY_PORTER_HANDLER_PRIORITY_MAX (often * %WOCKY_PORTER_HANDLER_PRIORITY_NORMAL). Handlers with a higher priority * (larger number) are called first. - * @callback: A #WockyPorterHandlerFunc, which should return %FALSE to decline - * the stanza (Wocky will continue to the next handler, if any), or %TRUE to - * stop further processing. + * @callback: (scope notified): A #WockyPorterHandlerFunc, which should return + * %FALSE to decline the stanza (Wocky will continue to the next handler, if + * any), or %TRUE to stop further processing. * @user_data: Passed to @callback. * @ap: a wocky_stanza_build() specification. The handler * will match a stanza only if the stanza received is a superset of the one @@ -396,9 +476,9 @@ wocky_porter_register_handler_from_va (WockyPorter *self, * %WOCKY_PORTER_HANDLER_PRIORITY_MAX (often * %WOCKY_PORTER_HANDLER_PRIORITY_NORMAL). Handlers with a higher priority * (larger number) are called first. - * @callback: A #WockyPorterHandlerFunc, which should return %FALSE to decline - * the stanza (Wocky will continue to the next handler, if any), or %TRUE to - * stop further processing. + * @callback: (scope notified): A #WockyPorterHandlerFunc, which should return + * %FALSE to decline the stanza (Wocky will continue to the next handler, if + * any), or %TRUE to stop further processing. * @user_data: Passed to @callback. * @stanza: a #WockyStanza. The handler will match a stanza only if * the stanza received is a superset of the one passed to this @@ -450,9 +530,9 @@ wocky_porter_register_handler_from_by_stanza (WockyPorter *self, * %WOCKY_PORTER_HANDLER_PRIORITY_MAX (often * %WOCKY_PORTER_HANDLER_PRIORITY_NORMAL). Handlers with a higher priority * (larger number) are called first. - * @callback: A #WockyPorterHandlerFunc, which should return %FALSE to decline - * the stanza (Wocky will continue to the next handler, if any), or %TRUE to - * stop further processing. + * @callback: (scope notified): A #WockyPorterHandlerFunc, which should return + * %FALSE to decline the stanza (Wocky will continue to the next handler, if + * any), or %TRUE to stop further processing. * @user_data: Passed to @callback. * @...: a wocky_stanza_build() specification. The handler * will match a stanza only if the stanza received is a superset of the one @@ -528,9 +608,9 @@ wocky_porter_register_handler_from (WockyPorter *self, * %WOCKY_PORTER_HANDLER_PRIORITY_MAX (often * %WOCKY_PORTER_HANDLER_PRIORITY_NORMAL). Handlers with a higher priority * (larger number) are called first. - * @callback: A #WockyPorterHandlerFunc, which should return %FALSE to decline - * the stanza (Wocky will continue to the next handler, if any), or %TRUE to - * stop further processing. + * @callback: (scope notified): A #WockyPorterHandlerFunc, which should return + * %FALSE to decline the stanza (Wocky will continue to the next handler, if + * any), or %TRUE to stop further processing. * @user_data: Passed to @callback. * @ap: a wocky_stanza_build() specification. The handler * will match a stanza only if the stanza received is a superset of the one @@ -592,9 +672,9 @@ wocky_porter_register_handler_from_anyone_va ( * %WOCKY_PORTER_HANDLER_PRIORITY_MAX (often * %WOCKY_PORTER_HANDLER_PRIORITY_NORMAL). Handlers with a higher priority * (larger number) are called first. - * @callback: A #WockyPorterHandlerFunc, which should return %FALSE to decline - * the stanza (Wocky will continue to the next handler, if any), or %TRUE to - * stop further processing. + * @callback: (scope notified): A #WockyPorterHandlerFunc, which should return + * %FALSE to decline the stanza (Wocky will continue to the next handler, if + * any), or %TRUE to stop further processing. * @user_data: Passed to @callback. * @stanza: a #WockyStanza. The handler will match a stanza only if * the stanza received is a superset of the one passed to this @@ -644,9 +724,9 @@ wocky_porter_register_handler_from_anyone_by_stanza ( * %WOCKY_PORTER_HANDLER_PRIORITY_MAX (often * %WOCKY_PORTER_HANDLER_PRIORITY_NORMAL). Handlers with a higher priority * (larger number) are called first. - * @callback: A #WockyPorterHandlerFunc, which should return %FALSE to decline - * the stanza (Wocky will continue to the next handler, if any), or %TRUE to - * stop further processing. + * @callback: (scope notified): A #WockyPorterHandlerFunc, which should return + * %FALSE to decline the stanza (Wocky will continue to the next handler, if + * any), or %TRUE to stop further processing. * @user_data: Passed to @callback. * @...: a wocky_stanza_build() specification. The handler * will match a stanza only if the stanza received is a superset of the one @@ -709,7 +789,7 @@ wocky_porter_register_handler_from_anyone ( /** * wocky_porter_unregister_handler: - * @porter: a #WockyPorter + * @self: a #WockyPorter * @id: the id of the handler to unregister * * Unregister a registered handler. This handler won't be called when @@ -732,7 +812,7 @@ wocky_porter_unregister_handler (WockyPorter *self, /** * wocky_porter_close_async: - * @porter: a #WockyPorter + * @self: a #WockyPorter * @cancellable: optional #GCancellable object, %NULL to ignore * @callback: callback to call when the request is satisfied * @user_data: the data to pass to callback function @@ -763,7 +843,7 @@ wocky_porter_close_async (WockyPorter *self, /** * wocky_porter_close_finish: - * @porter: a #WockyPorter + * @self: a #WockyPorter * @result: a #GAsyncResult * @error: a #GError location to store the error occuring, or %NULL to ignore. * @@ -827,7 +907,7 @@ wocky_porter_send_iq_async (WockyPorter *self, * * Get the reply of an IQ query. * - * Returns: a reffed #WockyStanza on success, %NULL on error + * Returns: (transfer full): a reffed #WockyStanza on success, %NULL on error */ WockyStanza * wocky_porter_send_iq_finish (WockyPorter *self, diff --git a/wocky/wocky-porter.h b/wocky/wocky-porter.h index 6a852d25..03022e70 100644 --- a/wocky/wocky-porter.h +++ b/wocky/wocky-porter.h @@ -290,15 +290,15 @@ guint wocky_porter_register_handler_from_anyone ( gpointer user_data, ...) G_GNUC_NULL_TERMINATED; -void wocky_porter_unregister_handler (WockyPorter *porter, +void wocky_porter_unregister_handler (WockyPorter *self, guint id); -void wocky_porter_close_async (WockyPorter *porter, +void wocky_porter_close_async (WockyPorter *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); -gboolean wocky_porter_close_finish (WockyPorter *porter, +gboolean wocky_porter_close_finish (WockyPorter *self, GAsyncResult *result, GError **error); diff --git a/wocky/wocky-pubsub-helpers.c b/wocky/wocky-pubsub-helpers.c index 2202222d..757a8d54 100644 --- a/wocky/wocky-pubsub-helpers.c +++ b/wocky/wocky-pubsub-helpers.c @@ -45,7 +45,7 @@ * server. The server will then send the event stanza on to your * contacts who have the appropriate capability. * - * Returns: a new #WockyStanza pubsub event stanza; free with g_object_unref() + * Returns: (transfer full): a new #WockyStanza pubsub event stanza; free with g_object_unref() */ WockyStanza * wocky_pubsub_make_event_stanza (const gchar *node, @@ -90,7 +90,7 @@ wocky_pubsub_make_event_stanza (const gchar *node, * * * - * Returns: a new iq[type='set']/pubsub/publish/item stanza + * Returns: (transfer full): a new iq[type='set']/pubsub/publish/item stanza */ WockyStanza * wocky_pubsub_make_publish_stanza ( @@ -132,7 +132,7 @@ wocky_pubsub_make_publish_stanza ( * * * - * Returns: a new iq[type=@sub_type]/pubsub/@action stanza + * Returns: (transfer full): a new iq[type=@sub_type]/pubsub/@action stanza */ WockyStanza * wocky_pubsub_make_stanza ( diff --git a/wocky/wocky-pubsub-node.c b/wocky/wocky-pubsub-node.c index adf76e9b..fb9bb9c9 100644 --- a/wocky/wocky-pubsub-node.c +++ b/wocky/wocky-pubsub-node.c @@ -351,6 +351,20 @@ wocky_pubsub_node_get_name (WockyPubsubNode *self) return priv->name; } +/** + * wocky_pubsub_node_make_publish_stanza: + * @self: a #WockyPubsubNode + * @pubsub_out: a pointer to #WockyNode + * @publish_out: a pointer to #WockyNode + * @item_out: a pointer to #WockyNode + * + * Creates new IQ #WockyStanza to publish @self, setting @pubsub_out + * and @publish_out and @item_out pointers to corresponding newly created + * xml nodes within stanza. Technically it is a wrapper around a call to + * wocky_pubsub_make_publish_stanza() with @self's details populated. + * + * Returns: (transfer full): Newly created IQ #WockyStanza. + */ WockyStanza * wocky_pubsub_node_make_publish_stanza (WockyPubsubNode *self, WockyNode **pubsub_out, @@ -392,6 +406,19 @@ pubsub_node_make_action_stanza (WockyPubsubNode *self, return stanza; } +/** + * wocky_pubsub_node_make_subscribe_stanza: + * @self: a #WockyPubsubNode + * @jid: a JID which should be subscribed to @self + * @pubsub_node: a pointer to #WockyNode + * @subscribe_node: a pointer to #WockyNode + * + * Creates new IQ #WockyStanza to subscribe @jid to @self, setting @pubsub_node + * and @subscriptions_node pointers to corresponding newly created nodes within + * stanza. + * + * Returns: (transfer full): Newly created IQ #WockyStanza. + */ WockyStanza * wocky_pubsub_node_make_subscribe_stanza (WockyPubsubNode *self, const gchar *jid, @@ -450,7 +477,7 @@ subscribe_cb (GObject *source, * @self: a pubsub node * @jid: the JID to use as the subscribed JID (usually the connection's bare or * full JID); may not be %NULL - * @cancellable: optional GCancellable object, %NULL to ignore + * @cancellable: (nullable): optional GCancellable object, %NULL to ignore * @callback: a callback to call when the request is completed * @user_data: data to pass to @callback * @@ -488,6 +515,21 @@ wocky_pubsub_node_subscribe_finish (WockyPubsubNode *self, return g_task_propagate_pointer (G_TASK (result), error); } +/** + * wocky_pubsub_node_make_unsubscribe_stanza: + * @self: a #WockyPubsubNode + * @jid: a JID which should be unsubscribed from @self + * @subid: (nullable): optional SubID which should be unsubscribed, or %NULL + * @pubsub_node: (out)(optional): a pointer to #WockyNode + * @unsubscribe_node: (out)(optional): a pointer to #WockyNode + * + * Creates new IQ #WockyStanza to remove @jid's subscription, setting @pubsub_node + * and @subscriptions_node pointers to corresponding newly created nodes within + * stanza. Optional @subid may indicate that only specific content-based + * subscription should be removed. + * + * Returns: (transfer full): Newly created IQ #WockyStanza. + */ WockyStanza * wocky_pubsub_node_make_unsubscribe_stanza (WockyPubsubNode *self, const gchar *jid, @@ -540,7 +582,7 @@ pubsub_node_void_iq_cb (GObject *source, * @jid: the JID subscribed to @self (usually the connection's bare or * full JID); may not be %NULL * @subid: the identifier associated with the subscription - * @cancellable: optional GCancellable object, %NULL to ignore + * @cancellable: (nullable): optional GCancellable object, %NULL to ignore * @callback: a callback to call when the request is completed * @user_data: data to pass to @callback * @@ -579,6 +621,18 @@ wocky_pubsub_node_unsubscribe_finish (WockyPubsubNode *self, return g_task_propagate_boolean (G_TASK (result), error); } +/** + * wocky_pubsub_node_make_delete_stanza: + * @self: a #WockyPubsubNode + * @pubsub_node: a pointer to #WockyNode + * @delete_node: a pointer to #WockyNode + * + * Creates new IQ #WockyStanza to delete self's pubsub, setting @pubsub_node + * and @delete_node pointers to corresponding newly created xml nodes within + * stanza. + * + * Returns: (transfer full): Newly created IQ #WockyStanza. + */ WockyStanza * wocky_pubsub_node_make_delete_stanza ( WockyPubsubNode *self, @@ -616,6 +670,18 @@ wocky_pubsub_node_delete_finish (WockyPubsubNode *self, return g_task_propagate_boolean (G_TASK (result), error); } +/** + * wocky_pubsub_node_make_list_subscribers_stanza: + * @self: a #WockyPubsubNode + * @pubsub_node: a pointer to #WockyNode + * @subscriptions_node: a pointer to #WockyNode + * + * Creates new IQ #WockyStanza to list @self's subscriptions, setting @pubsub_node + * and @subscriptions_node pointers to corresponding newly created nodes within + * stanza. + * + * Returns: (transfer full): Newly created IQ #WockyStanza. + */ WockyStanza * wocky_pubsub_node_make_list_subscribers_stanza ( WockyPubsubNode *self, @@ -659,7 +725,7 @@ list_subscribers_cb (GObject *source, /** * wocky_pubsub_node_list_subscribers_async: * @self: a pubsub node - * @cancellable: optional #GCancellable object + * @cancellable: (nullable): optional #GCancellable object * @callback: function to call when the subscribers have been retrieved or an * error has occured * @user_data: data to pass to @callback. @@ -693,8 +759,8 @@ wocky_pubsub_node_list_subscribers_async ( * wocky_pubsub_node_list_subscribers_finish: * @self: a pubsub node * @result: the result passed to a callback - * @subscribers: location at which to store a list of #WockyPubsubSubscription - * pointers, or %NULL + * @subscribers: (out)(element-type WockyPubsubSubscription)(optional): location + * at which to store a list of #WockyPubsubSubscription pointers, or %NULL * @error: location at which to store an error, or %NULL * * Completes a call to wocky_pubsub_node_list_subscribers_async(). The list @@ -721,6 +787,18 @@ wocky_pubsub_node_list_subscribers_finish ( return !g_task_had_error (G_TASK (result)); } +/** + * wocky_pubsub_node_make_list_affiliates_stanza: + * @self: a #WockyPubsubNode + * @pubsub_node: a pointer to #WockyNode + * @affiliations_node: a pointer to #WockyNode + * + * Creates new #WockyStanza representing @self, setting @pubsub_node and + * @affiliations_node pointers to corresponding newly created nodes within + * stanza. + * + * Returns: (transfer full): Newly created IQ #WockyStanza. + */ WockyStanza * wocky_pubsub_node_make_list_affiliates_stanza ( WockyPubsubNode *self, @@ -732,6 +810,14 @@ wocky_pubsub_node_make_list_affiliates_stanza ( pubsub_node, affiliations_node); } +/** + * wocky_pubsub_node_parse_affiliations: + * @self: a #WockyPubsubNode + * @affiliations_node: a #WockyNode from which to parse the <affiliations/> + * + * Returns: (transfer full)(element-type WockyPubsubAffiliation): a #GList of + * #WockyPubsubAffiliation objects parsed from @affiliations_node. + */ GList * wocky_pubsub_node_parse_affiliations ( WockyPubsubNode *self, @@ -807,7 +893,7 @@ list_affiliates_cb (GObject *source, /** * wocky_pubsub_node_list_affiliates_async: * @self: a pubsub node - * @cancellable: optional #GCancellable object + * @cancellable: (nullable): optional #GCancellable object * @callback: function to call when the affiliates have been retrieved or an * error has occured * @user_data: data to pass to @callback. @@ -841,8 +927,8 @@ wocky_pubsub_node_list_affiliates_async ( * wocky_pubsub_node_list_affiliates_finish: * @self: a pubsub node * @result: the result passed to a callback - * @affiliates: location at which to store a list of #WockyPubsubAffiliation - * pointers, or %NULL + * @affiliates: (out)(element-type WockyPubsubAffiliation)(optional): location + * at which to store a list of #WockyPubsubAffiliation pointers, or %NULL * @error: location at which to store an error, or %NULL * * Completes a call to wocky_pubsub_node_list_affiliates_async(). The list @@ -872,15 +958,15 @@ wocky_pubsub_node_list_affiliates_finish ( /** * wocky_pubsub_node_make_modify_affiliates_stanza: * @self: a pubsub node - * @affiliates: a list of #WockyPubsubAffiliation structures, describing only - * the affiliations which should be changed. + * @affiliates: (element-type WockyPubsubAffiliation): a list of #WockyPubsubAffiliation + * structures, describing only the affiliations which should be changed. * @pubsub_node: location at which to store a pointer to the <pubsub/> * node, or %NULL * @affiliations_node: location at which to store a pointer to the * <affiliations/> node, or %NULL * - * Returns: an IQ stanza to modify the entities affiliated to a node that you - * own. + * Returns: (transfer full): an IQ stanza to modify the entities affiliated to + * a node that you own. */ WockyStanza * wocky_pubsub_node_make_modify_affiliates_stanza ( @@ -943,8 +1029,9 @@ wocky_pubsub_node_make_modify_affiliates_stanza ( /** * wocky_pubsub_node_modify_affiliates_async: * @self: a pubsub node - * @affiliates: a list of #WockyPubsubAffiliation structures, describing only - * the affiliations which should be changed. + * @affiliates: (element-type WockyPubsubAffiliation): a list of + * #WockyPubsubAffiliation structures, describing only the affiliations + * which should be changed. * @cancellable: optional GCancellable object, %NULL to ignore * @callback: a callback to call when the request is completed * @user_data: data to pass to @callback @@ -998,7 +1085,7 @@ wocky_pubsub_node_modify_affiliates_finish ( * @configure_node: location at which to store a pointer to the * <configure/> node, or %NULL * - * Returns: an IQ stanza to retrieve the configuration of @self + * Returns: (transfer full): an IQ stanza to retrieve the configuration of @self */ WockyStanza * wocky_pubsub_node_make_get_configuration_stanza ( @@ -1072,8 +1159,8 @@ wocky_pubsub_node_get_configuration_async ( * * Complete a call to wocky_pubsub_node_get_configuration_async(). * - * Returns: a form representing the node configuration on success; %NULL and - * sets @error otherwise + * Returns: (transfer full): a form representing the node configuration on success; + * %NULL and sets @error otherwise */ WockyDataForm * wocky_pubsub_node_get_configuration_finish ( @@ -1086,6 +1173,15 @@ wocky_pubsub_node_get_configuration_finish ( return g_task_propagate_pointer (G_TASK (result), error); } +/** + * wocky_pubsub_node_get_porter: + * @self: a #WockyPubsubNode + * + * Obtain a #WockyPorter associated with @self. + * + * Returns: (transfer none): a #WockyPorter which is used to communicate with + * PubSub server. + */ WockyPorter * wocky_pubsub_node_get_porter (WockyPubsubNode *self) { @@ -1197,13 +1293,14 @@ wocky_pubsub_affiliation_free (WockyPubsubAffiliation *aff) /** * wocky_pubsub_affiliation_list_copy: - * @affs: a list of #WockyPubsubAffiliation + * @affs: (element-type WockyPubsubAffiliation): a list of #WockyPubsubAffiliation * * Shorthand for manually copying @affs, duplicating each element with * wocky_pubsub_affiliation_copy(). * - * Returns: a deep copy of @affs, which should ultimately be freed with - * wocky_pubsub_affiliation_list_free(). + * Returns: (transfer full)(element-type WockyPubsubAffiliation): a deep copy + * of @affs, which should ultimately be freed with + * wocky_pubsub_affiliation_list_free(). */ GList * wocky_pubsub_affiliation_list_copy (GList *affs) @@ -1214,7 +1311,7 @@ wocky_pubsub_affiliation_list_copy (GList *affs) /** * wocky_pubsub_affiliation_list_free: - * @affs: a list of #WockyPubsubAffiliation + * @affs: (element-type WockyPubsubAffiliation): a list of #WockyPubsubAffiliation * * Frees a list of WockyPubsubAffiliation structures, as shorthand for calling * wocky_pubsub_affiliation_free() for each element, followed by g_list_free(). diff --git a/wocky/wocky-pubsub-node.h b/wocky/wocky-pubsub-node.h index 2a57a398..c331eea6 100644 --- a/wocky/wocky-pubsub-node.h +++ b/wocky/wocky-pubsub-node.h @@ -47,6 +47,11 @@ struct _WockyPubsubNodeClass { GObjectClass parent_class; }; +/** + * WockyPubsubNode: + * + * The class representing pubsub node and providing API to manage the node. + */ struct _WockyPubsubNode { /**/ GObject parent; diff --git a/wocky/wocky-pubsub-service.c b/wocky/wocky-pubsub-service.c index 893ac284..933a29c7 100644 --- a/wocky/wocky-pubsub-service.c +++ b/wocky/wocky-pubsub-service.c @@ -297,6 +297,7 @@ wocky_pubsub_service_class_init ( /** * WockyPubsubService::node-deleted + * @service: a pubsub service * @node: a pubsub node * @stanza: the message/event stanza in its entirety * @event_node: the event node from @stanza @@ -430,8 +431,8 @@ pubsub_service_create_node (WockyPubsubService *self, * that this does not ensure that a node exists on the server; it merely * ensures a local representation. * - * Returns: a new reference to an object representing a node named @name on - * @self + * Returns: (transfer full): a new reference to an object representing a node + * named @name on @self */ WockyPubsubNode * wocky_pubsub_service_ensure_node (WockyPubsubService *self, @@ -457,7 +458,7 @@ wocky_pubsub_service_ensure_node (WockyPubsubService *self, * already exists; if not, returns %NULL. Note that this does not check whether * a node exists on the server; it only checks for a local representation. * - * Returns: a borrowed reference to a node, or %NULL + * Returns: (transfer none): a borrowed reference to a node, or %NULL */ WockyPubsubNode * wocky_pubsub_service_lookup_node (WockyPubsubService *self, @@ -533,6 +534,17 @@ default_configuration_iq_cb (GObject *source, g_object_unref (task); } +/** + * wocky_pubsub_service_get_default_node_configuration_async: + * @self: a #WockyPubsubService + * @cancellable: (nullable): a #GCancellable object, %NULL to ignore + * @callback: a callback to call when the request is completed + * @user_data: data to pass to @callback + * + * Requests a server for default node configuration for the @self's PubSub + * service. Use wocky_pubsub_service_get_default_node_configuration_finish() + * to complete the call and obtain the requested configuration data form. + */ void wocky_pubsub_service_get_default_node_configuration_async ( WockyPubsubService *self, @@ -555,6 +567,18 @@ wocky_pubsub_service_get_default_node_configuration_async ( g_object_unref (stanza); } +/** + * wocky_pubsub_service_get_default_node_configuration_finish: + * @self: a #WockyPubsubService + * @result: a #GAsyncResult + * @error: a pointer to #GError to populate the error details, or %NULL + * + * Completes the call to wocky_pubsub_service_get_default_node_configuration_async() + * and returns retrieved config as parsed data form. + * + * Returns: (transfer full): a #WockyDataForm with retrieved configuration if + * the call succeeds. Otherwise %NULL and @error is set accordingly. + */ WockyDataForm * wocky_pubsub_service_get_default_node_configuration_finish ( WockyPubsubService *self, @@ -619,6 +643,19 @@ wocky_pubsub_service_parse_subscription (WockyPubsubService *self, return sub; } +/** + * wocky_pubsub_service_parse_subscriptions: + * @self: a WockyPubsubService + * @subscriptions_node: a #WockyNode to parse + * @subscription_nodes: (out)(transfer container)(element-type WockyNode): a + * pointer to #GList filled with parsed nodes. Free with g_list_free. + * + * Parse subscription nodes from @subscriptions_node and populate pointers to + * the parsed nodes into @subscription_nodes. + * + * Returns: (transfer full)(element-type WockyPubsubSubscription): a list of + * #WockyPubsubSubscription from @subscriptions_node. + */ GList * wocky_pubsub_service_parse_subscriptions (WockyPubsubService *self, WockyNode *subscriptions_node, @@ -688,6 +725,19 @@ receive_subscriptions_cb (GObject *source, g_object_unref (task); } +/** + * wocky_pubsub_service_create_retrieve_subscriptions_stanza: + * @self: a #WockyPubsubService + * @node: a #WockyPubsubNode for which to create retrieval stanza + * @pubsub_node: a pointer to #WockyNode + * @subscriptions_node: a pointer to #WockyNode + * + * Creates new IQ #WockyStanza to request current subscriptions for @node on + * @self's pubsub service, setting @pubsub_node and @subscriptions_node + * pointers to corresponding newly created xml nodes within stanza. + * + * Returns: (transfer full): Newly created IQ #WockyStanza. + */ WockyStanza * wocky_pubsub_service_create_retrieve_subscriptions_stanza ( WockyPubsubService *self, @@ -712,6 +762,18 @@ wocky_pubsub_service_create_retrieve_subscriptions_stanza ( return stanza; } +/** + * wocky_pubsub_service_retrieve_subscriptions_async: + * @self: a #WockyPubsubService + * @node: a #WockyPubsubNode + * @cancellable: (nullable): a #GCancellable object, %NULL to ignore + * @callback: a callback to call when the request is completed + * @user_data: data to pass to @callback + * + * Requests a server for a list of subscriptions for the @node. Use + * wocky_pubsub_service_retrieve_subscriptions_finish() to complete the call + * and obtain the requested subscriptions. + */ void wocky_pubsub_service_retrieve_subscriptions_async ( WockyPubsubService *self, @@ -733,6 +795,20 @@ wocky_pubsub_service_retrieve_subscriptions_async ( g_object_unref (stanza); } +/** + * wocky_pubsub_service_retrieve_subscriptions_finish: + * @self: a #WockyPubsubService + * @result: a #GAsyncResult + * @subscriptions: (out)(transfer full)(element-type WockyPubsubSubscription)(optional): + * a #GList pointer set to a retrieval results on success, or %NULL. + * + * Completes the call to wocky_pubsub_service_retrieve_subscriptions_async() + * and populates the results into @subscriptions, unless it is %NULL. + * + * Returns: %TRUE if the call to the server was successful, %FALSE otherwise. + * When %TRUE the @subscriptions is populated with results, otherwise @error + * is set approprietly. + */ gboolean wocky_pubsub_service_retrieve_subscriptions_finish ( WockyPubsubService *self, @@ -765,8 +841,8 @@ wocky_pubsub_service_retrieve_subscriptions_finish ( * but it may also tell you "hey, you asked for 'ringo', but I gave you * 'george'". Good times. * - * Returns: a pubsub node if the reply made sense, or %NULL with @error set if - * not. + * Returns: (transfer full): a pubsub node if the reply made sense, or %NULL + * with @error set if not. */ WockyPubsubNode * wocky_pubsub_service_handle_create_node_reply ( @@ -851,6 +927,21 @@ create_node_iq_cb (GObject *source, g_object_unref (task); } +/** + * wocky_pubsub_service_create_create_node_stanza: + * @self: a #WockyPubsubService + * @name: a name for node to create + * @config: a #WockyDataForm with node's config + * @pubsub_node: a pointer to #WockyNode + * @create_node: a pointer to #WockyNode + * + * Creates new IQ #WockyStanza to create a new node on self's pubsub service, + * setting @pubsub_node and @create_node pointers to corresponding newly + * created xml nodes within stanza. A new node is created with name from @name + * parameter and config section populated from @config data form. + * + * Returns: (transfer full): Newly created IQ #WockyStanza. + */ WockyStanza * wocky_pubsub_service_create_create_node_stanza ( WockyPubsubService *self, @@ -882,6 +973,19 @@ wocky_pubsub_service_create_create_node_stanza ( return stanza; } +/** + * wocky_pubsub_service_create_node_async: + * @self: a #WockyPubsubService + * @name: a name for node to create + * @config: a #WockyDataForm with node's config + * @cancellable: (nullable): a #GCancellable object, %NULL to ignore + * @callback: a callback to call when the request is completed + * @user_data: data to pass to @callback + * + * Creates a new node with @name and @config on @self's service. Use + * wocky_pubsub_service_create_node_finish() to complete the call and + * obtain the created node. + */ void wocky_pubsub_service_create_node_async (WockyPubsubService *self, const gchar *name, @@ -903,6 +1007,17 @@ wocky_pubsub_service_create_node_async (WockyPubsubService *self, g_object_unref (stanza); } +/** + * wocky_pubsub_service_create_node_finish: + * @self: a #WockyPubsubService + * @result: a #GAsyncResult + * @error: location at which to store an error, if one occurred. + * + * Complete a call to wocky_pubsub_service_create_node_async(). + * + * Returns: (transfer full): a #WockyPubsubNode represesnting newly created + * node on success; %NULL and sets @error otherwise + */ WockyPubsubNode * wocky_pubsub_service_create_node_finish (WockyPubsubService *self, GAsyncResult *result, @@ -913,6 +1028,15 @@ wocky_pubsub_service_create_node_finish (WockyPubsubService *self, return g_task_propagate_pointer (G_TASK (result), error); } +/** + * wocky_pubsub_service_get_porter: + * @self: a #WockyPubsubService + * + * Obtain a #WockyPorter associated with @self. + * + * Returns: (transfer none): a #WockyPorter which is used to communicate with + * PubSub server. + */ WockyPorter * wocky_pubsub_service_get_porter (WockyPubsubService *self) { @@ -958,6 +1082,17 @@ wocky_pubsub_subscription_free (WockyPubsubSubscription *sub) g_slice_free (WockyPubsubSubscription, sub); } +/** + * wocky_pubsub_subscription_list_copy: + * @subs: (element-type WockyPubsubSubscription): a list of #WockyPubsubSubscription + * + * Shorthand for manually copying @subs, duplicating each element with + * wocky_pubsub_subscription_copy(). + * + * Returns: (transfer full)(element-type WockyPubsubSubscription): a deep copy + * of @subs, which should ultimately be freed with + * wocky_pubsub_subscription_list_free(). + */ GList * wocky_pubsub_subscription_list_copy (GList *subs) { @@ -965,6 +1100,13 @@ wocky_pubsub_subscription_list_copy (GList *subs) subs); } +/** + * wocky_pubsub_subscription_list_free: + * @subs: (element-type WockyPubsubSubscription): a list of #WockyPubsubSubscription + * + * Frees a list of #WockyPubsubSubscription structures, as shorthand for calling + * wocky_pubsub_subscription_free() for each element, followed by g_list_free(). + */ void wocky_pubsub_subscription_list_free (GList *subs) { diff --git a/wocky/wocky-resource-contact.c b/wocky/wocky-resource-contact.c index a2f8afc8..fddd2d0d 100644 --- a/wocky/wocky-resource-contact.c +++ b/wocky/wocky-resource-contact.c @@ -235,6 +235,14 @@ wocky_resource_contact_get_resource (WockyResourceContact *self) return priv->resource; } +/** + * wocky_resource_contact_get_bare_contact: + * @self: a #WockyResourceContact + * + * Obtains bare contact + * + * Returns: (transfer none): a pointer to internal #WockyBareContact + */ WockyBareContact * wocky_resource_contact_get_bare_contact (WockyResourceContact *self) { diff --git a/wocky/wocky-resource-contact.h b/wocky/wocky-resource-contact.h index e7749ff5..2a3cda51 100644 --- a/wocky/wocky-resource-contact.h +++ b/wocky/wocky-resource-contact.h @@ -47,6 +47,11 @@ struct _WockyResourceContactClass { WockyContactClass parent_class; }; +/** + * WockyResourceContact: + * + * A simple wrapper for the online resource of the roster contact. + */ struct _WockyResourceContact { /**/ WockyContact parent; @@ -76,10 +81,10 @@ WockyResourceContact * wocky_resource_contact_new (WockyBareContact *bare, const gchar *resource); const gchar * wocky_resource_contact_get_resource ( - WockyResourceContact *contact); + WockyResourceContact *self); WockyBareContact * wocky_resource_contact_get_bare_contact ( - WockyResourceContact *contact); + WockyResourceContact *self); gboolean wocky_resource_contact_equal (WockyResourceContact *a, WockyResourceContact *b); diff --git a/wocky/wocky-roster.c b/wocky/wocky-roster.c index 8ad3bbe8..d3b4f7e4 100644 --- a/wocky/wocky-roster.c +++ b/wocky/wocky-roster.c @@ -715,6 +715,16 @@ wocky_roster_fetch_roster_finish (WockyRoster *self, return g_task_propagate_boolean (G_TASK (result), error); } +/** + * wocky_roster_get_contact: + * @self: a #WockyRoster + * @jid: a contact JID + * + * Find a contact corresponding to the @jid in the roster. + * + * Returns: (transfer none): a #WockyBareContact corresponding to the @jid, + * or %NULL if it does not exist. + */ WockyBareContact * wocky_roster_get_contact (WockyRoster *self, const gchar *jid) @@ -724,6 +734,15 @@ wocky_roster_get_contact (WockyRoster *self, return g_hash_table_lookup (priv->items, jid); } +/** + * wocky_roster_get_all_contacts: + * @self: a #WockyRoster + * + * Get all #WockyBareContact contacts on the giver roster by making a deep + * copy of the roster content. + * + * Returns: (transfer full)(element-type WockyBareContact): a #GSList of #WockyBareContact + */ GSList * wocky_roster_get_all_contacts (WockyRoster *self) { diff --git a/wocky/wocky-roster.h b/wocky/wocky-roster.h index 6ca8d7a4..f4a0a455 100644 --- a/wocky/wocky-roster.h +++ b/wocky/wocky-roster.h @@ -115,8 +115,6 @@ typedef enum { WOCKY_ROSTER_ERROR_NOT_IN_ROSTER, } WockyRosterError; -GQuark wocky_roster_error_quark (void); - /** * WOCKY_ROSTER_ERROR: * diff --git a/wocky/wocky-sasl-auth.h b/wocky/wocky-sasl-auth.h index 28efa8a2..ac6e0cef 100644 --- a/wocky/wocky-sasl-auth.h +++ b/wocky/wocky-sasl-auth.h @@ -92,9 +92,6 @@ gboolean wocky_sasl_auth_authenticate_finish (WockySaslAuth *sasl, GAsyncResult *result, GError **error); -void -wocky_sasl_auth_add_handler (WockySaslAuth *auth, WockyAuthHandler *handler); - G_END_DECLS #endif /* #ifndef __WOCKY_SASL_AUTH_H__*/ diff --git a/wocky/wocky-sasl-scram.c b/wocky/wocky-sasl-scram.c index b1ef2446..38c93277 100644 --- a/wocky/wocky-sasl-scram.c +++ b/wocky/wocky-sasl-scram.c @@ -34,6 +34,21 @@ #define WOCKY_DEBUG_FLAG WOCKY_DEBUG_AUTH #include "wocky-debug-internal.h" +/** + * SECTION: wocky-sasl-scram + * @title: WockySaslScram + * @short_description: SASL SCRAM Mechanism Handler + * @see_also: #WockyAuthHandler, #WockyAuthRegistry, #WockySaslAuth + * + * WockySaslScram implements #WockyAuthHandler interface to handle SASL SCRAM + * mechanism described in RFC6120 (XMPP wire format) and RFC5802 (SASL message + * format). In addition to methods of #WockyAuthHandler which discribe client- + * side authentication, it also implements server-side methods. Server-side is + * mostly implemented to support test units, but could be used as generic sasl + * implementation for server. + */ + + typedef enum { WOCKY_SASL_SCRAM_STATE_STARTED, WOCKY_SASL_SCRAM_STATE_SERVER_FIRST_MESSAGE, @@ -203,18 +218,51 @@ wocky_sasl_scram_class_init ( object_class->set_property = wocky_sasl_scram_set_property; object_class->get_property = wocky_sasl_scram_get_property; + /** + * WockySaslScram:hash-algo: + * + * A hashing algorithm to use for SCRAM challenge computation. Should be of + * type #GChecksumType and be at least G_CHECKSUM_SHA1. Defaults to + * G_CHECKSUM_SHA256 if not specified. + * + * Since: 0.19.0 + */ g_object_class_install_property (object_class, PROP_HASH_ALGO, g_param_spec_int ("hash-algo", "hash algorithm", "The type of the Hash Algorithm to use for HMAC from GChecksumType", G_CHECKSUM_SHA1, G_CHECKSUM_SHA512, G_CHECKSUM_SHA256, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + /** + * WockySaslScram:cb-type: + * + * TLS channel binding type, as in #WockyTLSBindingType. CB support and its + * supported type is supposed to be identified in #WockySaslAuth and passed + * down to #WockyAuthRegistry which in turn creates #WockySaslScram with + * cb-type set to either WOCKY_TLS_BINDING_DISABLED (if our TLS layer does + * not support it), WOCKY_TLS_BINDING_NONE (if we do but server reports it + * does not support -PLUS methods) or supported binding type otherwise. + * + * Since: 0.19.0 + */ g_object_class_install_property (object_class, PROP_CB_TYPE, g_param_spec_enum ("cb-type", "binding type", "The type of the TLS Channel Binding to use in SASL negotiation", WOCKY_TYPE_TLS_BINDING_TYPE, WOCKY_TLS_BINDING_DISABLED, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + /** + * WockySaslScram:cb-data: (nullable) + * + * Contains tls channel binding data represented as Base64 encoded string. + * Must be set right after server advertised mechanisms, since theoretically + * it may change in time as re-keying happens. + * + * Must be %NULL if #WockySaslScram:cb-type is either WOCKY_TLS_BINDING_NONE + * or WOCKY_TLS_BINDING_DISABLED, may not be %NULL otherwise. + * + * Since: 0.19.0 + */ g_object_class_install_property (object_class, PROP_CB_DATA, g_param_spec_string ("cb-data", "binding data", "Base64 encoded TLS Channel binding data for the set type", NULL, @@ -225,11 +273,24 @@ wocky_sasl_scram_class_init ( "The name of the server we're authenticating to", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + /** + * WockySaslScram:username: + * + * Our authentication identity - a username, normally represented by + * localpart of the XMPP JID (see RFC6122). Might be overriden to be set + * to something else though, if server expects other type of the identity. + */ g_object_class_install_property (object_class, PROP_USERNAME, g_param_spec_string ("username", "username", "The username to authenticate with", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + /** + * WockySaslScram:password: + * + * A secret string (or simply password) to authenticate the provided + * identity (username). + */ g_object_class_install_property (object_class, PROP_PASSWORD, g_param_spec_string ("password", "password", "The password to authenticate with", NULL, @@ -268,6 +329,16 @@ wocky_sasl_scram_init (WockySaslScram *self) self->priv->state = WOCKY_SASL_SCRAM_STATE_STARTED; } +/** + * wocky_sasl_scram_new: + * @server: a server name (unused) + * @username: Auth identity name + * @password: Auth secret string + * + * Conveninece method to create a new instance of the object. + * + * Returns: (transfer full): a new #WockySaslScram object. + */ WockySaslScram * wocky_sasl_scram_new ( const gchar *server, @@ -742,13 +813,29 @@ scram_handle_success (WockyAuthHandler *handler, return FALSE; } -/** - * SASL Server implementation - */ +/* ** SASL Server implementation ** */ /* p=tls-server-end-point,, */ #define GS2_LEN 25 +/** + * wocky_sasl_scram_server_start_async: + * @self: a #WockySaslScram + * @message: SASL SCRAM Client-First message + * @cb: a callback to call once finished processing + * @cancel: (nullable): a #GCancellable or %NULL + * @data: user data to pass to @cb callback + * + * Starts server-side SASL SCRAM processing by parsing client-first @message + * and populating server-side context. Important: when creating server-side + * #WockySaslScram object the params #WockySaslScram:username and + * #WockySaslScram:password must be %NULL. This method will populate the + * #WockySaslScram:username and call @cb callback for the caller to validate + * username, lookup the password in IdP, set it and complete the server-start + * by calling wocky_sasl_scram_server_start_finish(). + * + * Since: 0.19.0 + */ void wocky_sasl_scram_server_start_async (WockySaslScram *self, gchar *message, @@ -837,6 +924,20 @@ wocky_sasl_scram_server_start_async (WockySaslScram *self, g_object_unref (task); } +/** + * wocky_sasl_scram_server_start_finish: + * @self: a #WockySaslScram + * @res: a #GAsyncResult + * @error: a location of #GError to set on error + * + * Finishes server-start processing by calculating necessary crypto keys + * and prepares server-first message. + * + * Returns: (transfer full): server-first message in SASL SCRAM format, eg.: + * `"r=client-nonceserver-nonce,s=randomsalt,i=9999"`. + * + * Since: 0.19.0 + */ gchar * wocky_sasl_scram_server_start_finish (WockySaslScram *self, GAsyncResult *res, @@ -882,13 +983,28 @@ wocky_sasl_scram_server_start_finish (WockySaslScram *self, g_assert (priv->nonce == NULL); priv->nonce = sasl_generate_base64_nonce (); - priv->server_first_bare = g_strdup_printf ("r=%s%s,s=%s,i=%lu", priv->client_nonce, + priv->server_first_bare = g_strdup_printf ("r=%s%s,s=%s,i=%"G_GUINT64_FORMAT"", priv->client_nonce, priv->nonce, priv->salt, priv->iterations); priv->state = WOCKY_SASL_SCRAM_STATE_SERVER_FIRST_MESSAGE; } return priv->server_first_bare; } +/** + * wocky_sasl_scram_server_step_async: + * @self: a #WockySaslScram + * @message: SASL SCRAM Client-Final message + * @cb: a callback to call once finished processing + * @cancel: (nullable): a #GCancellable or %NULL + * @data: user data to pass to @cb callback + * + * Continues server-side SASL SCRAM processing by parsing client-final + * @message and populating server-side context. Mainly responsible for + * parsing, validation and preparing auth-message. Actual proof calculation + * and verification is performed in wocky_sasl_scram_server_step_finish(). + * + * Since: 0.19.0 + */ void wocky_sasl_scram_server_step_async (WockySaslScram *self, gchar *message, @@ -975,6 +1091,22 @@ wocky_sasl_scram_server_step_async (WockySaslScram *self, g_task_return_pointer (task, g_strdup (p), g_free); } +/** + * wocky_sasl_scram_server_step_finish: + * @self: a #WockySaslScram + * @res: a #GAsyncResult + * @error: a location of #GError to set on error + * + * Finishes server-step processing by calculating necessary proofs and keys, + * compares it with those sent by the client and prepares server-final + * message. While it does assert identity validity internally, the session + * authentication completes only after client also accepts server proof. + * + * Returns: (transfer full): server-final message in SASL SCRAM format, eg.: + * `"v=serverproof"`. + * + * Since: 0.19.0 + */ gchar * wocky_sasl_scram_server_step_finish (WockySaslScram *self, GAsyncResult *res, diff --git a/wocky/wocky-sasl-scram.h b/wocky/wocky-sasl-scram.h index 211a8cd1..b30cd06a 100644 --- a/wocky/wocky-sasl-scram.h +++ b/wocky/wocky-sasl-scram.h @@ -30,8 +30,11 @@ G_BEGIN_DECLS +typedef struct _WockySaslScramClass WockySaslScramClass; +typedef struct _WockySaslScram WockySaslScram; + #define WOCKY_TYPE_SASL_SCRAM \ - wocky_sasl_scram_get_type () + (wocky_sasl_scram_get_type ()) #define WOCKY_SASL_SCRAM(obj) \ (G_TYPE_CHECK_INSTANCE_CAST ((obj), WOCKY_TYPE_SASL_SCRAM, \ @@ -53,16 +56,14 @@ G_BEGIN_DECLS typedef struct _WockySaslScramPrivate WockySaslScramPrivate; -typedef struct -{ +struct _WockySaslScram { GObject parent; WockySaslScramPrivate *priv; -} WockySaslScram; +}; -typedef struct -{ +struct _WockySaslScramClass { GObjectClass parent_class; -} WockySaslScramClass; +}; GType wocky_sasl_scram_get_type (void); diff --git a/wocky/wocky-sasl-utils.c b/wocky/wocky-sasl-utils.c index 56f643b4..8d74a40a 100644 --- a/wocky/wocky-sasl-utils.c +++ b/wocky/wocky-sasl-utils.c @@ -43,6 +43,19 @@ sasl_generate_base64_nonce (void) return g_base64_encode ((guchar *) n, sizeof (n)); } +/** + * sasl_calculate_hmac: + * @digest_type: a GChecksumType of the hash function + * @key: a HMAC key + * @key_len: length of the key byte string + * @text: a HMAC data + * @text_len: length of the data byte string + * + * Technically a wrapper around GHmac and its methods to produce + * a @digest_type HMAC of the @data by the @key. + * + * Returns: (transfer full): a new GByteArray containing HMAC value. + */ GByteArray * sasl_calculate_hmac (GChecksumType digest_type, guint8 *key, diff --git a/wocky/wocky-session.c b/wocky/wocky-session.c index d3dc08bf..36d132d7 100644 --- a/wocky/wocky-session.c +++ b/wocky/wocky-session.c @@ -144,6 +144,36 @@ wocky_session_get_property (GObject *object, } } +static void +wocky_session_full_jid_notify (GObject *source, + GParamSpec *pspec, + gpointer user_data) +{ + WockySession *self = WOCKY_SESSION (user_data); + WockySessionPrivate *priv = wocky_session_get_instance_private (self); + + g_clear_pointer (&priv->full_jid, g_free); + g_object_get (source, + "full-jid", &priv->full_jid, + NULL); + g_object_notify (G_OBJECT (self), "full-jid"); +} + +static void +wocky_session_connection_notify (GObject *source, + GParamSpec *pspec, + gpointer user_data) +{ + WockySession *self = WOCKY_SESSION (user_data); + WockySessionPrivate *priv = wocky_session_get_instance_private (self); + + g_clear_object (&priv->connection); + g_object_get (source, + "connection", &priv->connection, + NULL); + g_object_notify (G_OBJECT (self), "connection"); +} + static void wocky_session_constructed (GObject *object) { @@ -151,7 +181,13 @@ wocky_session_constructed (GObject *object) WockySessionPrivate *priv = self->priv; if (priv->connection != NULL) - priv->porter = wocky_c2s_porter_new (priv->connection, priv->full_jid); + { + priv->porter = wocky_c2s_porter_new (priv->connection, priv->full_jid); + g_signal_connect (priv->porter, "notify::full-jid", + G_CALLBACK (wocky_session_full_jid_notify), self); + g_signal_connect (priv->porter, "notify::connection", + G_CALLBACK (wocky_session_connection_notify), self); + } else priv->porter = wocky_meta_porter_new (priv->full_jid, priv->contact_factory); } @@ -173,6 +209,7 @@ wocky_session_dispose (GObject *object) priv->connection = NULL; } + g_signal_handlers_disconnect_by_data (priv->porter, self); g_object_unref (priv->porter); g_object_unref (priv->contact_factory); @@ -257,6 +294,14 @@ void wocky_session_start (WockySession *self) wocky_porter_start (priv->porter); } +/** + * wocky_session_get_porter: + * @session: a #WockySession + * + * Get @session's connection porter + * + * Returns: (transfer none): @session's #WockyPorter instance + */ WockyPorter * wocky_session_get_porter (WockySession *self) { @@ -265,6 +310,14 @@ wocky_session_get_porter (WockySession *self) return priv->porter; } +/** + * wocky_session_get_contact_factory: + * @session: a #WockySession + * + * Get @session's contact factory + * + * Returns: (transfer none): @session's #WockyContactFactory + */ WockyContactFactory * wocky_session_get_contact_factory (WockySession *self) { diff --git a/wocky/wocky-session.h b/wocky/wocky-session.h index 95fde2f8..247d8a27 100644 --- a/wocky/wocky-session.h +++ b/wocky/wocky-session.h @@ -47,6 +47,11 @@ struct _WockySessionClass { GObjectClass parent_class; }; +/** + * WockySession: + * + * The #WockySession itself. + */ struct _WockySession { /**/ GObject parent; diff --git a/wocky/wocky-stanza.c b/wocky/wocky-stanza.c index 2e5fd566..057f1b79 100644 --- a/wocky/wocky-stanza.c +++ b/wocky/wocky-stanza.c @@ -79,6 +79,8 @@ static StanzaTypeName type_names[NUM_WOCKY_STANZA_TYPE] = WOCKY_XMPP_NS_SASL_AUTH }, { WOCKY_STANZA_TYPE_STREAM_ERROR, "error", WOCKY_XMPP_NS_STREAM }, + { WOCKY_STANZA_TYPE_SM_ENABLED, "enabled", + WOCKY_XMPP_NS_SM3 }, { WOCKY_STANZA_TYPE_UNKNOWN, NULL, NULL }, }; @@ -209,6 +211,14 @@ wocky_stanza_new (const gchar *name, return result; } +/** + * wocky_stanza_copy: + * @old: a #WockyStanza to copy + * + * Performs full copy of the @old + * + * Returns: (transfer full): A new #WockyStanza which is a full copy of @old + */ WockyStanza * wocky_stanza_copy (WockyStanza *old) { @@ -354,7 +364,7 @@ wocky_stanza_new_with_sub_type (WockyStanzaType type, * NULL); * ]| * - * Returns: a new stanza object + * Returns: (transfer full): a new stanza object */ WockyStanza * wocky_stanza_build (WockyStanzaType type, @@ -374,6 +384,21 @@ wocky_stanza_build (WockyStanzaType type, return stanza; } +/** + * wocky_stanza_build_to_contact: + * @type: The type of stanza to build + * @sub_type: The stanza's subtype; valid values depend on @type. (For instance, + * #WOCKY_STANZA_TYPE_IQ can use #WOCKY_STANZA_SUB_TYPE_GET, but not + * #WOCKY_STANZA_SUB_TYPE_SUBSCRIBED.) + * @from: The sender's JID, or %NULL to leave it unspecified. + * @to: The target's #WockyContact, or %NULL to leave it unspecified. + * @...: the description of the stanza to build, terminated with %NULL + * + * See wocky_stanza_build() for details, this convenience wrapper merely + * converts @to from #WockyContact to JID and calls wocky_stanza_build(). + * + * Returns: (transfer full): a new #WockyStanza object. + */ WockyStanza * wocky_stanza_build_to_contact (WockyStanzaType type, WockyStanzaSubType sub_type, @@ -400,6 +425,21 @@ wocky_stanza_build_to_contact (WockyStanzaType type, return stanza; } +/** + * wocky_stanza_build_va: + * @type: The type of stanza to build + * @sub_type: The stanza's subtype; valid values depend on @type. (For instance, + * #WOCKY_STANZA_TYPE_IQ can use #WOCKY_STANZA_SUB_TYPE_GET, but not + * #WOCKY_STANZA_SUB_TYPE_SUBSCRIBED.) + * @from: The sender's JID, or %NULL to leave it unspecified. + * @to: The target's JID, or %NULL to leave it unspecified. + * @ap: the description of the stanza to build, expressed as #va_list + * + * See wocky_stanza_build() for details, this is actual implementation of the + * stanza build function called by other wrappers. + * + * Returns: (transfer full): a new #WockyStanza object. + */ WockyStanza * wocky_stanza_build_va (WockyStanzaType type, WockyStanzaSubType sub_type, @@ -546,6 +586,17 @@ create_iq_reply (WockyStanza *iq, return reply; } +/** + * wocky_stanza_build_iq_result: + * @iq: a #WockyStanza for which to build a result + * @...: %NULL terminated build descriptors + * + * Builds IQ result stanza for the given @iq, optionally adding other nodes + * following wocky_stanza_build() descriptors. See + * wocky_stanza_build_iq_error() for examples. + * + * Returns: (transfer full): a #WockyStanza with result for @iq. + */ WockyStanza * wocky_stanza_build_iq_result (WockyStanza *iq, ...) @@ -560,6 +611,15 @@ wocky_stanza_build_iq_result (WockyStanza *iq, return reply; } +/** + * wocky_stanza_build_iq_result_va: + * @iq: a #WockyStanza for which to build a result + * @ap: a #va_list with build descriptors + * + * For details see wocky_stanza_build_iq_result(), which wraps this method. + * + * Returns: (transfer full): a #WockyStanza with result for @iq. + */ WockyStanza * wocky_stanza_build_iq_result_va ( WockyStanza *iq, @@ -596,7 +656,7 @@ wocky_stanza_build_iq_result_va ( * ')', NULL); * ]| * - * Returns: an error reply for @iq + * Returns: (transfer full): a #WockyStanza error reply for @iq */ WockyStanza * wocky_stanza_build_iq_error (WockyStanza *iq, @@ -639,6 +699,16 @@ wocky_stanza_build_iq_error (WockyStanza *iq, return reply; } +/** + * wocky_stanza_build_iq_error_va: + * @iq: a #WockyStanza with IQ to respond to + * @ap: a #va_list with building descriptors. + * + * Similar to wocky_stanza_build_iq_error() but does not perform additional + * query extraction and uses #va_list. + * + * Returns: (transfer full): a #WockyStanza with error for @iq. + */ WockyStanza * wocky_stanza_build_iq_error_va ( WockyStanza *iq, @@ -731,9 +801,11 @@ wocky_stanza_extract_stream_error (WockyStanza *stanza, /** * wocky_stanza_get_top_node: - * @self: a stanza + * @self: a #WockyStanza * - * Returns: A pointer to the topmost node of the stanza + * + * + * Returns: (transfer none): A pointer to the topmost node of the stanza */ WockyNode * wocky_stanza_get_top_node (WockyStanza *self) @@ -775,6 +847,15 @@ wocky_stanza_get_to (WockyStanza *self) return wocky_node_get_attribute (wocky_stanza_get_top_node (self), "to"); } +/** + * wocky_stanza_get_to_contact: + * @self: a #WockyStanza + * + * + * + * Returns: (transfer none): a pointer to internal #WockyContact representing + * `to` attribute. + */ WockyContact * wocky_stanza_get_to_contact (WockyStanza *self) { @@ -784,6 +865,15 @@ wocky_stanza_get_to_contact (WockyStanza *self) return self->priv->to_contact; } +/** + * wocky_stanza_get_from_contact: + * @self: a #WockyStanza + * + * + * + * Returns: (transfer none): a pointer to internal #WockyContact representing + * `from` attribute. + */ WockyContact * wocky_stanza_get_from_contact (WockyStanza *self) { diff --git a/wocky/wocky-stanza.h b/wocky/wocky-stanza.h index bcca6700..e8142999 100644 --- a/wocky/wocky-stanza.h +++ b/wocky/wocky-stanza.h @@ -104,6 +104,7 @@ typedef enum WOCKY_STANZA_TYPE_SUCCESS, WOCKY_STANZA_TYPE_FAILURE, WOCKY_STANZA_TYPE_STREAM_ERROR, + WOCKY_STANZA_TYPE_SM_ENABLED, WOCKY_STANZA_TYPE_UNKNOWN, /*< private >*/ NUM_WOCKY_STANZA_TYPE diff --git a/wocky/wocky-tls-common.c b/wocky/wocky-tls-common.c index bf7d0c62..f29ebf31 100644 --- a/wocky/wocky-tls-common.c +++ b/wocky/wocky-tls-common.c @@ -35,17 +35,6 @@ wocky_tls_cert_error_quark (void) return quark; } -GQuark -wocky_tls_error_quark (void) -{ - static GQuark quark = 0; - - if (quark == 0) - quark = g_quark_from_static_string ("wocky-tls-error"); - - return quark; -} - /* this file is "borrowed" from an unmerged gnio feature: */ /* Local Variables: */ /* c-file-style: "gnu" */ diff --git a/wocky/wocky-tls-connector.c b/wocky/wocky-tls-connector.c index 324a00ce..35bcb413 100644 --- a/wocky/wocky-tls-connector.c +++ b/wocky/wocky-tls-connector.c @@ -447,6 +447,31 @@ wocky_tls_connector_new (WockyTLSHandler *handler) "tls-handler", handler, NULL); } +/** + * wocky_tls_connector_secure_async: + * @self: a #WockyTLSConnector instance + * @connection: a #WockyXmppConnection + * @old_style_ssl: indicates whether we use old-style (xmpps or direct-tls) + * or new-style (xmpp+starttls) securing method. + * @peername: a string representing primary server identity - which in most + * cases is a domain part of the JID. + * @extra_identities: a #GStrv list of alternative names to match CN/SAN + * against during certificate validation. In case XMPP subdomain is not + * listed in the SAN/CN but otherwise certificate is trusted/valid. + * @cancellable: (nullable): a #GCancellable, or %NULL + * @callback: a #GAsyncReadyCallback to call once TLS handshake is complete + * @user_data: the data to pass to the callback function + * + * Secure XMPP @connection by wrapping it into TLS and performing handshake + * with TLS authentication (certificate validation) using #WockyTLSHandler + * passed into constructor. + * + * Note: `old_style_ssl` could be a bit misleading, it refers to older RFC3920 + * specification which used DirectTLS (XMPPS) and was replaced by newer + * RFC6120 mandating StartTLS TLS mechanism. The "old" DirectTLS however was + * later re-introduced by XEP-0368 and is now actively supported by the + * servers to mitigate potential RFC7590 non-conformance of the clients. + */ void wocky_tls_connector_secure_async (WockyTLSConnector *self, WockyXmppConnection *connection, @@ -479,6 +504,18 @@ wocky_tls_connector_secure_async (WockyTLSConnector *self, do_starttls (self); } +/** + * wocky_tls_connector_secure_finish: + * @self: a #WockyTLSConnector instance + * @res: a #GAsyncResult + * @error: a pointer to #GError, or %NULL + * + * Finish an asynchronous connection TLS wrapping by finishing the handshake. + * + * Returns: (transfer full): #WockyXmppConnection representing TLS wrapped + * original connection, or %NULL if TLS authentication (handshake) fails. + * In which case @error will be set accordingly. + */ WockyXmppConnection * wocky_tls_connector_secure_finish (WockyTLSConnector *self, GAsyncResult *result, diff --git a/wocky/wocky-tls.c b/wocky/wocky-tls.c index f22522a0..057aff4f 100644 --- a/wocky/wocky-tls.c +++ b/wocky/wocky-tls.c @@ -20,26 +20,49 @@ /** * SECTION: wocky-tls - * @title: Wocky GnuTLS TLS + * @title: Wocky TLS * @short_description: Establish TLS sessions * + * Since version 0.19 this implementation is a wrapper around GIO + * GTlsConnection - where it all started. Glib-networking is then + * providing actual TLS backends - GnuTLS (default) and OpenSSL (optional). + * + * #WockyTLSession thus became simple alias to GTlsConnection class. See + * + * GIO GTlsConnection API Reference for generic methods. + * * The below environment variables can be used to print debug output from GIO * and GNU TLS. - * G_MESSAGES_DEBUG=GLib-Net|all, - * GNUTLS_DEBUG_LEVEL=[0..99] + * + * * `G_MESSAGES_DEBUG=GLib-Net|all`: Overall glib debug (could be very chatty) + * * `GNUTLS_DEBUG_LEVEL=[0..99]`: low level GnuTLS debug messages (similar) + * * Higher values will print more information. See the documentation of - * gnutls_global_set_log_level for more details. + * `gnutls_global_set_log_level` for more details. * - * WOCKY_DEBUG=tls|all will trigger increased debugging output from within + * * `WOCKY_DEBUG=tls|all` will trigger increased debugging output from within * wocky-tls.c as well. * - * The G_TLS_GNUTLS_PRIORITY environment variable can be set to a gnutls - * priority string [See gnutls-cli(1) or the gnutls_priority_init docs] + * The `G_TLS_GNUTLS_PRIORITY` environment variable can be set to a gnutls + * priority string [See gnutls-cli(1) or the `gnutls_priority_init` docs] * to control most tls protocol details. An empty or unset value is roughly - * equivalent to a priority string "NORMAL:%COMPAT:-VERS-TLS1.0:-VERS-TLS1.1". + * equivalent to a priority string `"NORMAL:%COMPAT:-VERS-TLS1.0:-VERS-TLS1.1"`. + * + * To control OpenSSL backend's parameters following glib-networking variables + * could be set in the environment: + * * `G_TLS_OPENSSL_MAX_PROTO`: Sets OpenSSL Max allowed protocol. + * * `G_TLS_OPENSSL_CIPHER_LIST`: Sets allowed ciphers (cipherstring). + * * `G_TLS_OPENSSL_CURVE_LIST`: Similarly restricts allowed EC curve list. + * * `G_TLS_OPENSSL_SIGNATURE_ALGORITHM_LIST`: Controls allowed hash functions. + * + * For instance to restrict to TLSv1.2 protocol you can export following + * environment variable: `export G_TLS_OPENSSL_MAX_PROTO=0x0303` * - * Finally GIO_USE_TLS=gnutls|openssl could be used to control which backend - * will be selected (if available) [See glib-networking docs for details]. + * Finally `GIO_USE_TLS=gnutls|openssl` could be used to control which backend + * will be selected (if available). + * + * See glib-networking docs for details on controlling backends and their + * behaviour. */ #ifdef HAVE_CONFIG_H @@ -182,14 +205,27 @@ wocky_tls_session_add_crl (WockyTLSSession *session, const gchar *crl_path) DEBUG ("GIO-TLS currently doesn't support custom CRLs, ignoring %s", crl_path); } +/** + * wocky_tls_session_get_peers_certificate: + * @session: a #GTlsConnection + * @type: (out): a location for #WockyTLSCertType + * + * Obtains all peer certificates - which supposed to be server cert and the + * optional chain. The @type param is set with the type of retrieved certs, + * and should always be WOCKY_TLS_CERT_TYPE_X509 (since pgp is deprecated). + * Returned certificates are binary blobs of DER certeficate data. + * + * Returns: (transfer full)(element-type GByteArray): a #GPtrArray of #GByteArray + * DER blobs. + */ GPtrArray * -wocky_tls_session_get_peers_certificate (GTlsConnection *conn, +wocky_tls_session_get_peers_certificate (GTlsConnection *session, WockyTLSCertType *type) { GTlsCertificate *tlscert; GPtrArray *certificates; - tlscert = g_tls_connection_get_peer_certificate (conn); + tlscert = g_tls_connection_get_peer_certificate (session); if (tlscert == NULL) return NULL; diff --git a/wocky/wocky-tls.h b/wocky/wocky-tls.h index 892c49fd..0ed0dede 100644 --- a/wocky/wocky-tls.h +++ b/wocky/wocky-tls.h @@ -36,7 +36,7 @@ #define WOCKY_TYPE_TLS_SESSION G_TYPE_TLS_CONNECTION #define WOCKY_TLS_SESSION(inst) G_TLS_CONNECTION(inst) -typedef GTlsConnection WockyTLSSession; +typedef struct _GTlsConnection WockyTLSSession; typedef enum { @@ -47,10 +47,10 @@ typedef enum GQuark wocky_tls_cert_error_quark (void); #define WOCKY_TLS_CERT_ERROR (wocky_tls_cert_error_quark ()) - +/* GQuark wocky_tls_error_quark (void); #define WOCKY_TLS_ERROR (wocky_tls_error_quark ()) - +*/ typedef enum { WOCKY_TLS_CERT_OK = 0, diff --git a/wocky/wocky-types.h b/wocky/wocky-types.h index ff224266..25df02c9 100644 --- a/wocky/wocky-types.h +++ b/wocky/wocky-types.h @@ -27,12 +27,14 @@ G_BEGIN_DECLS +#ifndef __GTK_DOC_IGNORE__ typedef struct _WockyBareContact WockyBareContact; typedef struct _WockyLLContact WockyLLContact; typedef struct _WockyNodeTree WockyNodeTree; typedef struct _WockyResourceContact WockyResourceContact; typedef struct _WockySession WockySession; typedef struct _WockyPubsubNode WockyPubsubNode; +#endif /* __GTK_DOC_IGNORE__ */ G_END_DECLS diff --git a/wocky/wocky-utils.c b/wocky/wocky-utils.c index c3845b2d..948b079c 100644 --- a/wocky/wocky-utils.c +++ b/wocky/wocky-utils.c @@ -700,6 +700,16 @@ wocky_absolutize_path (const gchar *path) return ret; } +/** + * wocky_list_deep_copy: + * @copy: (scope call): a #GBoxedCopyFunc function + * @items: a #GList of GBoxed items + * + * Convenience function wrapper to make a full deep copy of input list @items + * with each item copied using @copy function. + * + * Returns: a new #GList with copies of all @items. + */ GList * wocky_list_deep_copy (GBoxedCopyFunc copy, GList *items) diff --git a/wocky/wocky-xep-0115-capabilities.h b/wocky/wocky-xep-0115-capabilities.h index 4506ff46..34bc2e4b 100644 --- a/wocky/wocky-xep-0115-capabilities.h +++ b/wocky/wocky-xep-0115-capabilities.h @@ -56,6 +56,15 @@ typedef gboolean (*WockyXep0115CapabilitiesHasFeatureFunc) ( WockyXep0115Capabilities *contact, const gchar *feature); +/** + * wocky_xep_0115_capabilities_get_data_forms: + * @contact: an object implementing #WockyXep0115Capabilities interface + * + * Get the forms from the advertised capabilities list + * + * Returns: (element-type WockyDataForm)(transfer none)(nullable): An array with + * data forms or NULL if there was none. + */ const GPtrArray * wocky_xep_0115_capabilities_get_data_forms ( WockyXep0115Capabilities *contact); diff --git a/wocky/wocky-xmpp-connection.c b/wocky/wocky-xmpp-connection.c index 28179359..2c867909 100644 --- a/wocky/wocky-xmpp-connection.c +++ b/wocky/wocky-xmpp-connection.c @@ -808,30 +808,14 @@ wocky_xmpp_connection_recv_stanza_async (WockyXmppConnection *connection, return; } -/** - * wocky_xmpp_connection_recv_stanza_finish: - * @connection: a #WockyXmppConnection. - * @result: a GAsyncResult. - * @error: a GError location to store the error occuring, or NULL to ignore. - * - * Finishes receiving a stanza - * - * Returns: A #WockyStanza or NULL on error (unref after usage) - */ - -WockyStanza * -wocky_xmpp_connection_recv_stanza_finish (WockyXmppConnection *connection, - GAsyncResult *result, +static WockyStanza * +retrieve_stanza (WockyXmppConnection *connection, + WockyStanza *(*getter)(WockyXmppReader *), GError **error) { WockyXmppConnectionPrivate *priv; WockyStanza *stanza = NULL; - g_return_val_if_fail (g_task_is_valid (result, connection), NULL); - - if (!g_task_propagate_boolean (G_TASK (result), error)) - return NULL; - priv = connection->priv; switch (wocky_xmpp_reader_get_state (priv->reader)) @@ -840,7 +824,7 @@ wocky_xmpp_connection_recv_stanza_finish (WockyXmppConnection *connection, g_assert_not_reached (); break; case WOCKY_XMPP_READER_STATE_OPENED: - stanza = wocky_xmpp_reader_pop_stanza (priv->reader); + stanza = getter (priv->reader); break; case WOCKY_XMPP_READER_STATE_CLOSED: g_set_error_literal (error, WOCKY_XMPP_CONNECTION_ERROR, @@ -864,6 +848,79 @@ wocky_xmpp_connection_recv_stanza_finish (WockyXmppConnection *connection, return stanza; } +/** + * wocky_xmpp_connection_peek_stanza_async: + * @connection: a #WockyXmppConnection + * @cancellable: optional GCancellable object, NULL to ignore. + * @callback: callback to call when the request is satisfied. + * @user_data: the data to pass to callback function. + * + * Asynchronous receive a #WockyStanza. When the operation is + * finished @callback will be called. You can then call + * wocky_xmpp_connection_peek_stanza_finish() to get the result of + * the operation. + * + * Can only be called after wocky_xmpp_connection_peek_open_async has finished + * its operation. + */ +void +wocky_xmpp_connection_peek_stanza_async (WockyXmppConnection *connection, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + wocky_xmpp_connection_recv_stanza_async (connection, cancellable, callback, + user_data); +} + +/** + * wocky_xmpp_connection_peek_stanza_finish: + * @connection: a #WockyXmppConnection. + * @result: a GAsyncResult. + * @error: a GError location to store the error occuring, or NULL to ignore. + * + * Finishes receiving a stanza + * + * Returns: (transfer none): A #WockyStanza or NULL on error + */ + +const WockyStanza * +wocky_xmpp_connection_peek_stanza_finish (WockyXmppConnection *connection, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, connection), NULL); + + if (!g_task_propagate_boolean (G_TASK (result), error)) + return NULL; + + return retrieve_stanza (connection, wocky_xmpp_reader_peek_stanza, error); +} + +/** + * wocky_xmpp_connection_recv_stanza_finish: + * @connection: a #WockyXmppConnection. + * @result: a GAsyncResult. + * @error: a GError location to store the error occuring, or NULL to ignore. + * + * Finishes receiving a stanza + * + * Returns: (transfer full): A #WockyStanza or NULL on error (unref after usage) + */ + +WockyStanza * +wocky_xmpp_connection_recv_stanza_finish (WockyXmppConnection *connection, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, connection), NULL); + + if (!g_task_propagate_boolean (G_TASK (result), error)) + return NULL; + + return retrieve_stanza (connection, wocky_xmpp_reader_pop_stanza, error); +} + /** * wocky_xmpp_connection_send_close_async: * @connection: a #WockyXmppConnection. diff --git a/wocky/wocky-xmpp-connection.h b/wocky/wocky-xmpp-connection.h index edbd106d..6001c423 100644 --- a/wocky/wocky-xmpp-connection.h +++ b/wocky/wocky-xmpp-connection.h @@ -156,6 +156,16 @@ WockyStanza *wocky_xmpp_connection_recv_stanza_finish ( GAsyncResult *result, GError **error); +void wocky_xmpp_connection_peek_stanza_async (WockyXmppConnection *connection, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +const WockyStanza *wocky_xmpp_connection_peek_stanza_finish ( + WockyXmppConnection *connection, + GAsyncResult *result, + GError **error); + void wocky_xmpp_connection_send_close_async (WockyXmppConnection *connection, GCancellable *cancellable, GAsyncReadyCallback callback, diff --git a/wocky/wocky-xmpp-error.c b/wocky/wocky-xmpp-error.c index 485bfbf7..6208c973 100644 --- a/wocky/wocky-xmpp-error.c +++ b/wocky/wocky-xmpp-error.c @@ -240,9 +240,9 @@ wocky_xmpp_error_quark (void) /** * wocky_xmpp_error_string: - * @error: a core stanza error + * @error: a #WockyXmppError for core stanza error * - * + * Extracts error string (name) for the given enum @error value. * * Returns: the name of the tag corresponding to @error */ @@ -254,9 +254,9 @@ wocky_xmpp_error_string (WockyXmppError error) /** * wocky_xmpp_error_description: - * @error: a core stanza error + * @error: a #WockyXmppError for core stanza error * - * + * Extracts error description for the given enum @error value. * * Returns: a description of the error, in English, as specified in XMPP Core */ @@ -273,11 +273,11 @@ static GList *error_domains = NULL; /** * wocky_xmpp_error_register_domain - * @domain: a description of the error domain + * @domain: a #WockyXmppErrorDomain description of the error domain * * Registers a new set of application-specific stanza errors. This allows - * GErrors in that domain to be passed to wocky_stanza_error_to_node(), and to - * be recognized and returned by wocky_xmpp_error_extract() (and + * #GErrors in that domain to be passed to wocky_stanza_error_to_node(), + * and to be recognized and returned by wocky_xmpp_error_extract() (and * wocky_stanza_extract_errors(), by extension). */ void @@ -309,8 +309,8 @@ xmpp_error_find_domain (GQuark domain) * %WOCKY_JINGLE_ERROR). * * Returns the name of the XMPP stanza error element represented by @error. - * This is intended for use in debugging messages, with %GErrors returned by - * wocky_stanza_extract_errors(). + * This is intended for use in debugging messages, with %GErrors + * returned by wocky_stanza_extract_errors(). * * Returns: the error code as a string, or %NULL if * error->domain is not known to Wocky. @@ -537,7 +537,7 @@ wocky_xmpp_error_extract (WockyNode *error, } /** - * wocky_g_error_to_node: + * wocky_stanza_error_to_node: * @error: an error in the domain #WOCKY_XMPP_ERROR, or in an * application-specific domain registered with * wocky_xmpp_error_register_domain() @@ -552,7 +552,7 @@ wocky_xmpp_error_extract (WockyNode *error, * There is currently no way to override the type='' of an XMPP Core stanza * error without creating an application-specific error code which does so. * - * Returns: the newly-created node + * Returns: (transfer none): a pointer to the newly-created <error/> node */ WockyNode * wocky_stanza_error_to_node (const GError *error, diff --git a/wocky/wocky-xmpp-reader.c b/wocky/wocky-xmpp-reader.c index cb1e967d..6bb444ad 100644 --- a/wocky/wocky-xmpp-reader.c +++ b/wocky/wocky-xmpp-reader.c @@ -754,8 +754,8 @@ wocky_xmpp_reader_push (WockyXmppReader *reader, const guint8 *data, * if there are no available stanzas. The stanza is not removed from the * readers queue * - * Returns: One #WockyStanza or NULL if there are no available stanzas. The - * stanza is owned by the #WockyXmppReader + * Returns: (transfer none)(nullable): One #WockyStanza or NULL if there are + * no available stanzas. */ WockyStanza * wocky_xmpp_reader_peek_stanza (WockyXmppReader *reader) @@ -772,8 +772,8 @@ wocky_xmpp_reader_peek_stanza (WockyXmppReader *reader) * Gets one #WockyStanza out of the reader or NULL if there are no * available stanzas. * - * Returns: One #WockyStanza or NULL if there are no available stanzas. - * Caller owns the returned stanza. + * Returns: (transfer full)(nullable): One #WockyStanza or NULL if there are + * no available stanzas. */ WockyStanza * wocky_xmpp_reader_pop_stanza (WockyXmppReader *reader) diff --git a/wocky/wocky.h b/wocky/wocky.h index 7bb8580d..2c6540d5 100644 --- a/wocky/wocky.h +++ b/wocky/wocky.h @@ -90,7 +90,8 @@ #define WOCKY_API_VER_0_0 0x0 #define WOCKY_API_VER_0_1 (G_ENCODE_VERSION(0,1)) -#define WOCKY_API_VERSION WOCKY_API_VER_0_1 +#define WOCKY_API_VER_0_2 (G_ENCODE_VERSION(0,2)) +#define WOCKY_API_VERSION WOCKY_API_VER_0_2 G_BEGIN_DECLS