diff options
author | Mikhail Zabaluev <mikhail.zabaluev@nokia.com> | 2011-01-03 17:45:15 +0200 |
---|---|---|
committer | Mikhail Zabaluev <mikhail.zabaluev@nokia.com> | 2011-01-03 17:45:15 +0200 |
commit | 248b7a0c9b0efac2653c3e2659cbec1eda01d238 (patch) | |
tree | 4a272fbf3aa1ccc8d0c3cf496b0e1ca994692911 | |
parent | c89168ecb97280c6e1f732c659c800af832bd7ab (diff) | |
parent | 6ef183a45800b000fe1c402255e822963f90dc0c (diff) |
Merge branch 'master' into messages-retouch
32 files changed, 2421 insertions, 782 deletions
diff --git a/Makefile.am b/Makefile.am index 242d97d..aab3064 100644 --- a/Makefile.am +++ b/Makefile.am @@ -24,14 +24,9 @@ SUBDIRS = \ src \ tests -dist-hook: - chmod u+w ${distdir}/ChangeLog - if test -d ${top_srcdir}/.git; then \ - git --work-tree=${top_srcdir} log --stat > ${distdir}/ChangeLog || \ - git --work-tree=${top_srcdir} log > ${distdir}/ChangeLog; \ - fi - include tools/lcov.am LCOV_CHECK_ARGS = CHECK_TWISTED_SLEEP=6 clean-local: lcov-clean + +include tools/telepathy.am @@ -7,6 +7,18 @@ recent at the top). See also ChangeLog. +telepathy-sofiasip 0.7.0 (2010-11-23) +------------------------------------- + +The "Fermentation Vat No. 78" release. + +- Opening the new development series. +- Implemented o.fd.Tp.Ch.I.Messages (fd.o #29377). +- Implemented o.fd.Tp.Protocol (fd.o #30538). +- Uses TpDBusDaemon to export media streams (fd.o #31720). +- Depends on telepathy-glib 0.12.0 now. +- Improvements in session state and signalling. + Changes in release 0.6.3 (2010-06-21) ------------------------------------- diff --git a/configure.ac b/configure.ac index a08d34b..a3a5fb8 100644 --- a/configure.ac +++ b/configure.ac @@ -2,8 +2,8 @@ AC_PREREQ([2.59]) m4_define([THIS_PACKAGE],[telepathy-sofiasip]) m4_define([VERSION_MAJOR],[0]) -m4_define([VERSION_MINOR],[6]) -m4_define([VERSION_MICRO],[99]) +m4_define([VERSION_MINOR],[7]) +m4_define([VERSION_MICRO],[0]) m4_define([VERSION_NANO],[1]) m4_define([BASE_VERSION],[VERSION_MAJOR.VERSION_MINOR.VERSION_MICRO]) @@ -85,7 +85,7 @@ AC_SUBST(SOFIA_SIP_UA_CFLAGS) AC_SUBST(SOFIA_SIP_UA_VERSION) dnl Check for telepathy-glib -PKG_CHECK_MODULES(TELEPATHY_GLIB, [telepathy-glib >= 0.12]) +PKG_CHECK_MODULES(TELEPATHY_GLIB, [telepathy-glib >= 0.13.9]) AC_SUBST(TELEPATHY_GLIB_CFLAGS) AC_SUBST(TELEPATHY_GLIB_LIBS) diff --git a/src/Makefile.am b/src/Makefile.am index 6684513..ca15a87 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -92,6 +92,8 @@ libtpsip_convenience_la_SOURCES = \ debug.c \ media-factory.h \ media-factory.c \ + protocol.h \ + protocol.c \ text-factory.h \ text-factory.c \ sip-connection-helpers.h \ diff --git a/src/media-factory.c b/src/media-factory.c index 689d773..f4573c5 100644 --- a/src/media-factory.c +++ b/src/media-factory.c @@ -451,15 +451,15 @@ static const gchar * const named_channel_allowed_properties[] = { NULL }; -/* not advertised in foreach_channel_class - can only be requested with +/* not advertised in type_foreach_channel_class - can only be requested with * RequestChannel, not with CreateChannel/EnsureChannel */ static const gchar * const anon_channel_allowed_properties[] = { NULL }; static void -tpsip_media_factory_foreach_channel_class (TpChannelManager *manager, - TpChannelManagerChannelClassFunc func, +tpsip_media_factory_type_foreach_channel_class (GType type, + TpChannelManagerTypeChannelClassFunc func, gpointer user_data) { GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal, @@ -476,7 +476,7 @@ tpsip_media_factory_foreach_channel_class (TpChannelManager *manager, handle_type_value); g_value_set_uint (handle_type_value, TP_HANDLE_TYPE_CONTACT); - func (manager, table, named_channel_allowed_properties, user_data); + func (type, table, named_channel_allowed_properties, user_data); g_hash_table_destroy (table); } @@ -688,7 +688,8 @@ channel_manager_iface_init (gpointer g_iface, TpChannelManagerIface *iface = g_iface; iface->foreach_channel = tpsip_media_factory_foreach_channel; - iface->foreach_channel_class = tpsip_media_factory_foreach_channel_class; + iface->type_foreach_channel_class = + tpsip_media_factory_type_foreach_channel_class; iface->request_channel = tpsip_media_factory_request_channel; iface->create_channel = tpsip_media_factory_create_channel; iface->ensure_channel = tpsip_media_factory_ensure_channel; diff --git a/src/protocol.c b/src/protocol.c new file mode 100644 index 0000000..d9c2383 --- /dev/null +++ b/src/protocol.c @@ -0,0 +1,527 @@ +/* + * protocol.c - source for TpsipProtocol + * Copyright (C) 2007-2010 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "protocol.h" + +#include <string.h> + +#include <telepathy-glib/telepathy-glib.h> +#include <dbus/dbus-protocol.h> +#include <dbus/dbus-glib.h> + +#include <tpsip/sofia-decls.h> +#include <sofia-sip/su_glib.h> + +#define DEBUG_FLAG TPSIP_DEBUG_CONNECTION +#include "debug.h" +#include "media-factory.h" +#include "sip-connection.h" +#include "sip-connection-helpers.h" +#include "text-factory.h" + +#define PROTOCOL_NAME "sip" +#define ICON_NAME "im-" PROTOCOL_NAME +#define VCARD_FIELD_NAME "x-" PROTOCOL_NAME +#define ENGLISH_NAME "SIP" + +G_DEFINE_TYPE (TpsipProtocol, + tpsip_protocol, + TP_TYPE_BASE_PROTOCOL) + +enum { + PROP_SOFIA_ROOT = 1, +}; + +struct _TpsipProtocolPrivate +{ + su_root_t *sofia_root; +}; + +/* Used in the otherwise-unused offset field of the TpCMParamSpec. The first + * one is nonzero to catch implicit zero-initialization. */ +enum { + PARAM_EASY = 1, + PARAM_SET_SEPARATELY +}; + +static TpCMParamSpec tpsip_params[] = { + /* Account (a sip: URI) + * + * FIXME: validate account SIP URI properly, using appropriate RFCs */ + { "account", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, + TP_CONN_MGR_PARAM_FLAG_REQUIRED | TP_CONN_MGR_PARAM_FLAG_REGISTER, + NULL, PARAM_SET_SEPARATELY, tp_cm_param_filter_string_nonempty, NULL }, + + /* Username to register with, if different than in the account URI */ + { "auth-user", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, + 0, NULL, PARAM_EASY }, + + /* Password */ + { "password", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, + TP_CONN_MGR_PARAM_FLAG_SECRET, NULL, PARAM_EASY }, + + /* Display name for self */ + { "alias", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL, PARAM_EASY, + /* setting a 0-length alias makes no sense */ + tp_cm_param_filter_string_nonempty, NULL }, + + /* Registrar */ + { "registrar", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL, + PARAM_EASY }, + + /* Used to compose proxy URI */ + { "proxy-host", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL, + PARAM_SET_SEPARATELY }, + { "port", DBUS_TYPE_UINT16_AS_STRING, G_TYPE_UINT, + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(SIP_DEFAULT_PORT), + PARAM_SET_SEPARATELY, tp_cm_param_filter_uint_nonzero }, + { "transport", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, "auto", PARAM_SET_SEPARATELY }, + + /* Enables loose routing as per RFC 3261 */ + { "loose-routing", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN, + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(FALSE), + PARAM_EASY }, + + /* Used to enable proactive NAT traversal techniques */ + { "discover-binding", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN, + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(TRUE), + PARAM_EASY }, + + /* Mechanism used for connection keepalive maintenance */ + { "keepalive-mechanism", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, "auto", PARAM_SET_SEPARATELY }, + + /* Keep-alive interval */ + { "keepalive-interval", DBUS_TYPE_UINT32_AS_STRING, G_TYPE_UINT, + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(0), PARAM_EASY }, + + /* Use SRV DNS lookup to discover STUN server */ + { "discover-stun", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN, + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(TRUE), PARAM_EASY }, + + /* STUN server */ + { "stun-server", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL, + PARAM_EASY }, + + /* STUN port */ + { "stun-port", DBUS_TYPE_UINT16_AS_STRING, G_TYPE_UINT, + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, + GUINT_TO_POINTER(TPSIP_DEFAULT_STUN_PORT), PARAM_EASY, + tp_cm_param_filter_uint_nonzero }, + + /* If the session content cannot be modified once initially set up */ + { "immutable-streams", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN, + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(FALSE), + PARAM_EASY }, + + /* Local IP address to use, workaround purposes only */ + { "local-ip-address", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, + 0, NULL, PARAM_EASY }, + + /* Local port for SIP, workaround purposes only */ + { "local-port", DBUS_TYPE_UINT16_AS_STRING, G_TYPE_UINT, 0, NULL, + PARAM_EASY }, + + /* Extra-realm authentication */ + { "extra-auth-user", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, + 0, NULL, PARAM_EASY }, + { "extra-auth-password", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, + TP_CONN_MGR_PARAM_FLAG_SECRET, NULL, PARAM_EASY }, + + { NULL } +}; + +static void +tpsip_protocol_init (TpsipProtocol *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TPSIP_TYPE_PROTOCOL, + TpsipProtocolPrivate); +} + +static const TpCMParamSpec * +get_parameters (TpBaseProtocol *self G_GNUC_UNUSED) +{ + return tpsip_params; +} + +static TpsipConnectionKeepaliveMechanism +priv_parse_keepalive (const gchar *str) +{ + if (str == NULL || strcmp (str, "auto") == 0) + return TPSIP_CONNECTION_KEEPALIVE_AUTO; + if (strcmp (str, "register") == 0) + return TPSIP_CONNECTION_KEEPALIVE_REGISTER; + if (strcmp (str, "options") == 0) + return TPSIP_CONNECTION_KEEPALIVE_OPTIONS; + if (strcmp (str, "stun") == 0) + return TPSIP_CONNECTION_KEEPALIVE_STUN; + if (strcmp (str, "off") == 0) + return TPSIP_CONNECTION_KEEPALIVE_NONE; + + WARNING ("unsupported keepalive-method value \"%s\", falling back to auto", str); + return TPSIP_CONNECTION_KEEPALIVE_AUTO; +} + +static gchar * +priv_compose_proxy_uri (const gchar *host, + const gchar *transport, + guint port) +{ + const gchar *scheme = "sip"; + + if (host == NULL) + return NULL; + + /* Set scheme to SIPS if transport is TLS */ + + if (transport != NULL && !g_ascii_strcasecmp (transport, "tls")) + scheme = "sips"; + + /* Format the resulting URI */ + + if (port) + return g_strdup_printf ("%s:%s:%u", scheme, host, port); + else + return g_strdup_printf ("%s:%s", scheme, host); +} + +/** + * Returns a default SIP proxy address based on the public + * SIP address 'sip_address' and . For instance + * "sip:first.surname@company.com" would result in "sip:company.com". + * The SIP stack will further utilize DNS lookups to find the IP address + * for the SIP server responsible for the domain "company.com". + */ +static gchar * +priv_compose_default_proxy_uri (const gchar *sip_address, + const gchar *transport) +{ + char *result = NULL; + char *host; + char *found; + + g_return_val_if_fail (sip_address != NULL, NULL); + + /* skip sip and sips prefixes, updating transport if necessary */ + found = strchr (sip_address, ':'); + if (found != NULL) { + if (g_ascii_strncasecmp ("sip:", sip_address, 4) == 0) + ; + else if (g_ascii_strncasecmp ("sips:", sip_address, 5) == 0) + { + if (transport == NULL || + strcmp (transport, "auto") == 0) + transport = "tls"; + } + else + { + /* error, unknown URI prefix */ + return NULL; + } + + sip_address = found + 1; + } + + /* skip userinfo */ + found = strchr (sip_address, '@'); + if (found != NULL) + sip_address = found + 1; + + /* copy rest of the string */ + host = g_strdup (sip_address); + + /* mark end (before uri-parameters defs or headers) */ + found = strchr (host, ';'); + if (found == NULL) + found = strchr (host, '?'); + if (found != NULL) + *found = '\0'; + + result = priv_compose_proxy_uri (host, transport, 0); + + g_free (host); + + return result; +} + +static TpBaseConnection * +new_connection (TpBaseProtocol *protocol, + GHashTable *params, + GError **error) +{ + TpsipProtocol *self = TPSIP_PROTOCOL (protocol); + TpsipConnection *conn; + guint i; + const gchar *account; + const gchar *transport; + const gchar *proxy_host; + guint16 port; + gchar *proxy; + TpsipConnectionKeepaliveMechanism keepalive_mechanism; + + account = tp_asv_get_string (params, "account"); + transport = tp_asv_get_string (params, "transport"); + port = tp_asv_get_uint32 (params, "port", NULL); + + conn = g_object_new (TPSIP_TYPE_CONNECTION, + "protocol", PROTOCOL_NAME, + "sofia-root", self->priv->sofia_root, + "address", account, + NULL); + + proxy_host = tp_asv_get_string (params, "proxy-host"); + + if (tp_str_empty (proxy_host)) + { + proxy = priv_compose_default_proxy_uri (account, transport); + DEBUG("set outbound proxy address to <%s>, based on <%s>", proxy, + account); + } + else + { + proxy = priv_compose_proxy_uri (proxy_host, transport, port); + } + + g_object_set (conn, + "proxy", proxy, + NULL); + g_free (proxy); + + if (!tp_str_empty (transport) && tp_strdiff (transport, "auto")) + g_object_set (conn, + "transport", transport, + NULL); + + for (i = 0; tpsip_params[i].name != NULL; i++) + { + if (tpsip_params[i].offset == PARAM_SET_SEPARATELY) + { + DEBUG ("Parameter %s is handled specially", tpsip_params[i].name); + continue; + } + + g_assert (tpsip_params[i].offset == PARAM_EASY); + + switch (tpsip_params[i].gtype) + { + case G_TYPE_STRING: + { + const gchar *s = tp_asv_get_string (params, + tpsip_params[i].name); + + if (!tp_str_empty (s)) + g_object_set (conn, + tpsip_params[i].name, s, + NULL); + } + break; + + case G_TYPE_UINT: + { + gboolean valid = FALSE; + guint u = tp_asv_get_uint32 (params, + tpsip_params[i].name, &valid); + + if (valid) + g_object_set (conn, + tpsip_params[i].name, u, + NULL); + } + break; + + case G_TYPE_BOOLEAN: + { + gboolean valid = FALSE; + gboolean b = tp_asv_get_boolean (params, tpsip_params[i].name, + &valid); + + if (valid) + g_object_set (conn, + tpsip_params[i].name, b, + NULL); + } + break; + + default: + /* no other parameters have been written yet */ + g_assert_not_reached (); + } + } + + keepalive_mechanism = priv_parse_keepalive (tp_asv_get_string (params, + "keepalive-mechanism")); + g_object_set (conn, + "keepalive-mechanism", keepalive_mechanism, + NULL); + + return TP_BASE_CONNECTION (conn); +} + +static gchar * +normalize_contact (TpBaseProtocol *self G_GNUC_UNUSED, + const gchar *contact, + GError **error) +{ + return tpsip_normalize_contact (contact, NULL, NULL, error); +} + +static gchar * +identify_account (TpBaseProtocol *self G_GNUC_UNUSED, + GHashTable *asv, + GError **error) +{ + const gchar *account = tp_asv_get_string (asv, "account"); + + g_assert (account != NULL); + return g_strdup (account); +} + +static GStrv +get_interfaces (TpBaseProtocol *self) +{ + return g_new0 (gchar *, 1); +} + +static void +get_connection_details (TpBaseProtocol *self, + GStrv *connection_interfaces, + GType **channel_managers, + gchar **icon_name, + gchar **english_name, + gchar **vcard_field) +{ + if (connection_interfaces != NULL) + { + *connection_interfaces = g_strdupv ( + (GStrv) tpsip_connection_get_implemented_interfaces ()); + } + + if (channel_managers != NULL) + { + GType types[] = { + TPSIP_TYPE_TEXT_FACTORY, + TPSIP_TYPE_MEDIA_FACTORY, + G_TYPE_INVALID }; + + *channel_managers = g_memdup (types, sizeof(types)); + } + + if (icon_name != NULL) + { + *icon_name = g_strdup (ICON_NAME); + } + + if (vcard_field != NULL) + { + *vcard_field = g_strdup (VCARD_FIELD_NAME); + } + + if (english_name != NULL) + { + *english_name = g_strdup (ENGLISH_NAME); + } +} + +static GStrv +dup_authentication_types (TpBaseProtocol *base) +{ + const gchar * const types[] = { + TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION, + NULL + }; + + return g_strdupv ((GStrv) types); +} + +static void +tpsip_protocol_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TpsipProtocol *self = TPSIP_PROTOCOL (object); + + switch (property_id) + { + case PROP_SOFIA_ROOT: + g_value_set_pointer (value, self->priv->sofia_root); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +tpsip_protocol_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + TpsipProtocol *self = TPSIP_PROTOCOL (object); + + switch (property_id) + { + case PROP_SOFIA_ROOT: + self->priv->sofia_root = g_value_get_pointer (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +tpsip_protocol_class_init (TpsipProtocolClass *klass) +{ + TpBaseProtocolClass *base_class = (TpBaseProtocolClass *) klass; + GObjectClass *object_class = (GObjectClass *) klass; + GParamSpec *param_spec; + + g_type_class_add_private (klass, sizeof (TpsipProtocolPrivate)); + + base_class->get_parameters = get_parameters; + base_class->new_connection = new_connection; + base_class->normalize_contact = normalize_contact; + base_class->identify_account = identify_account; + base_class->get_interfaces = get_interfaces; + base_class->get_connection_details = get_connection_details; + base_class->dup_authentication_types = dup_authentication_types; + + object_class->get_property = tpsip_protocol_get_property; + object_class->set_property = tpsip_protocol_set_property; + + param_spec = g_param_spec_pointer ("sofia-root", "Sofia-SIP root", + "the root object for Sofia-SIP", + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_SOFIA_ROOT, + param_spec); +} + +TpBaseProtocol * +tpsip_protocol_new (su_root_t *sofia_root) +{ + return g_object_new (TPSIP_TYPE_PROTOCOL, + "name", PROTOCOL_NAME, + "sofia-root", sofia_root, + NULL); +} diff --git a/src/protocol.h b/src/protocol.h new file mode 100644 index 0000000..7470af1 --- /dev/null +++ b/src/protocol.h @@ -0,0 +1,75 @@ +/* + * protocol.h - header for TpsipProtocol + * Copyright (C) 2007-2010 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef TPSIP_PROTOCOL_H +#define TPSIP_PROTOCOL_H + +#include <glib-object.h> +#include <telepathy-glib/base-protocol.h> + +#include <tpsip/sofia-decls.h> +#include <sofia-sip/su_glib.h> + +G_BEGIN_DECLS + +typedef struct _TpsipProtocol TpsipProtocol; +typedef struct _TpsipProtocolPrivate TpsipProtocolPrivate; +typedef struct _TpsipProtocolClass TpsipProtocolClass; +typedef struct _TpsipProtocolClassPrivate TpsipProtocolClassPrivate; + +struct _TpsipProtocolClass { + TpBaseProtocolClass parent_class; + + TpsipProtocolClassPrivate *priv; +}; + +struct _TpsipProtocol { + TpBaseProtocol parent; + + TpsipProtocolPrivate *priv; +}; + +GType tpsip_protocol_get_type (void); + +#define TPSIP_TYPE_PROTOCOL \ + (tpsip_protocol_get_type ()) +#define TPSIP_PROTOCOL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TPSIP_TYPE_PROTOCOL, \ + TpsipProtocol)) +#define TPSIP_PROTOCOL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TPSIP_TYPE_PROTOCOL, \ + TpsipProtocolClass)) +#define TPSIP_IS_PROTOCOL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + TPSIP_TYPE_PROTOCOL)) +#define TPSIP_PROTOCOL_GET_CLASS(klass) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TPSIP_TYPE_PROTOCOL, \ + TpsipProtocolClass)) + +gchar *tpsip_protocol_normalize_contact (const gchar *id, + GError **error); + +TpBaseProtocol *tpsip_protocol_new (su_root_t *sofia_root); + +G_END_DECLS + +#endif diff --git a/src/sip-connection-helpers.c b/src/sip-connection-helpers.c index 0fda819..8ad9b42 100644 --- a/src/sip-connection-helpers.c +++ b/src/sip-connection-helpers.c @@ -775,14 +775,11 @@ priv_lowercase_url_part (su_home_t *home, const char *src) #define TPSIP_RESERVED_CHARS_ALLOWED_IN_USERNAME "!*'()&=+$,;?/" gchar * -tpsip_handle_normalize (TpHandleRepoIface *repo, - const gchar *sipuri, - gpointer context, - GError **error) +tpsip_normalize_contact (const gchar *sipuri, + const url_t *base_url, + const gchar *transport, + GError **error) { - TpsipConnection *conn = TPSIP_CONNECTION (context); - TpsipConnectionPrivate *priv = TPSIP_CONNECTION_GET_PRIVATE (conn); - const url_t *base_url = priv->account_url; su_home_t home[1] = { SU_HOME_INIT(home) }; url_t *url; gchar *retval = NULL; @@ -827,8 +824,8 @@ tpsip_handle_normalize (TpHandleRepoIface *repo, { /* Set the scheme to SIP or SIPS accordingly to the connection's * transport preference */ - if (priv->transport != NULL - && g_ascii_strcasecmp (priv->transport, "tls") == 0) + if (transport != NULL + && g_ascii_strcasecmp (transport, "tls") == 0) { url->url_type = url_sips; url->url_scheme = "sips"; @@ -879,6 +876,19 @@ error: return retval; } +gchar * +tpsip_handle_normalize (TpHandleRepoIface *repo, + const gchar *sipuri, + gpointer context, + GError **error) +{ + TpsipConnection *conn = TPSIP_CONNECTION (context); + TpsipConnectionPrivate *priv = TPSIP_CONNECTION_GET_PRIVATE (conn); + + return tpsip_normalize_contact (sipuri, priv->account_url, priv->transport, + error); +} + static GQuark tpsip_handle_url_quark () { diff --git a/src/sip-connection-helpers.h b/src/sip-connection-helpers.h index 327c13c..4644908 100644 --- a/src/sip-connection-helpers.h +++ b/src/sip-connection-helpers.h @@ -66,6 +66,11 @@ gchar * tpsip_handle_normalize (TpHandleRepoIface *repo, gpointer context, GError **error); +gchar *tpsip_normalize_contact (const gchar *sipuri, + const url_t *base_url, + const gchar *transport, + GError **error); + const url_t* tpsip_conn_get_contact_url (TpsipConnection *conn, TpHandle handle); diff --git a/src/sip-connection-manager.c b/src/sip-connection-manager.c index 7a33481..ecb882e 100644 --- a/src/sip-connection-manager.c +++ b/src/sip-connection-manager.c @@ -38,6 +38,7 @@ #include <tpsip/sofia-decls.h> #include <sofia-sip/su_glib.h> +#include "protocol.h" #include "sip-connection-manager.h" #include "sip-connection.h" @@ -48,165 +49,6 @@ G_DEFINE_TYPE(TpsipConnectionManager, tpsip_connection_manager, TP_TYPE_BASE_CONNECTION_MANAGER) -/* private structure *//* typedef struct _TpsipConnectionManagerPrivate TpsipConnectionManagerPrivate; */ - -typedef struct { - gchar *account; - gchar *auth_user; - gchar *password; - gchar *alias; - gchar *registrar; - gchar *proxy_host; - guint port; - gchar *transport; - gboolean loose_routing; - gboolean discover_binding; - gchar *keepalive_mechanism; - guint keepalive_interval; - gboolean discover_stun; - gchar *stun_server; - guint stun_port; - gboolean immutable_streams; - gchar *local_ip_address; - guint local_port; - gchar *extra_auth_user; - gchar *extra_auth_password; -} TpsipConnParams; - -static void * -alloc_params (void) -{ - return g_slice_new0 (TpsipConnParams); -} - -static void -free_params (void *p) -{ - TpsipConnParams *params = (TpsipConnParams *)p; - - g_free (params->account); - g_free (params->auth_user); - g_free (params->password); - g_free (params->alias); - g_free (params->registrar); - g_free (params->proxy_host); - g_free (params->transport); - g_free (params->keepalive_mechanism); - g_free (params->stun_server); - g_free (params->local_ip_address); - g_free (params->extra_auth_user); - g_free (params->extra_auth_password); - - g_slice_free (TpsipConnParams, params); -} - -enum { - TPSIP_CONN_PARAM_ACCOUNT = 0, - TPSIP_CONN_PARAM_AUTH_USER, - TPSIP_CONN_PARAM_PASSWORD, - TPSIP_CONN_PARAM_ALIAS, - TPSIP_CONN_PARAM_REGISTRAR, - TPSIP_CONN_PARAM_PROXY_HOST, - TPSIP_CONN_PARAM_PORT, - TPSIP_CONN_PARAM_TRANSPORT, - TPSIP_CONN_PARAM_LOOSE_ROUTING, - TPSIP_CONN_PARAM_DISCOVER_BINDING, - TPSIP_CONN_PARAM_KEEPALIVE_MECHANISM, - TPSIP_CONN_PARAM_KEEPALIVE_INTERVAL, - TPSIP_CONN_PARAM_DISCOVER_STUN, - TPSIP_CONN_PARAM_STUN_SERVER, - TPSIP_CONN_PARAM_STUN_PORT, - TPSIP_CONN_PARAM_IMMUTABLE_STREAMS, - TPSIP_CONN_PARAM_LOCAL_IP_ADDRESS, - TPSIP_CONN_PARAM_LOCAL_PORT, - TPSIP_CONN_PARAM_EXTRA_AUTH_USER, - TPSIP_CONN_PARAM_EXTRA_AUTH_PASSWORD, - N_TPSIP_CONN_PARAMS -}; - -static const TpCMParamSpec tpsip_params[] = { - /* Account (a sip: URI) */ - { "account", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, - TP_CONN_MGR_PARAM_FLAG_REQUIRED | TP_CONN_MGR_PARAM_FLAG_REGISTER, - NULL, G_STRUCT_OFFSET (TpsipConnParams, account) }, - /* Username to register with, if different than in the account URI */ - { "auth-user", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, - 0, NULL, G_STRUCT_OFFSET (TpsipConnParams, auth_user) }, - /* Password */ - { "password", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, - TP_CONN_MGR_PARAM_FLAG_SECRET, - NULL, G_STRUCT_OFFSET (TpsipConnParams, password) }, - /* Display name for self */ - { "alias", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL, - G_STRUCT_OFFSET(TpsipConnParams, alias), - /* setting a 0-length alias makes no sense */ - tp_cm_param_filter_string_nonempty, NULL }, - /* Registrar */ - { "registrar", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, - 0, NULL, G_STRUCT_OFFSET (TpsipConnParams, registrar) }, - /* Used to compose proxy URI */ - { "proxy-host", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, - 0, NULL, G_STRUCT_OFFSET (TpsipConnParams, proxy_host) }, - /* Used to compose proxy URI */ - { "port", DBUS_TYPE_UINT16_AS_STRING, G_TYPE_UINT, - TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(SIP_DEFAULT_PORT), - G_STRUCT_OFFSET (TpsipConnParams, port) }, - /* Used to compose proxy URI */ - { "transport", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, - TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, "auto", - G_STRUCT_OFFSET (TpsipConnParams, transport) }, - /* Enables loose routing as per RFC 3261 */ - { "loose-routing", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN, - TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(FALSE), - G_STRUCT_OFFSET (TpsipConnParams, loose_routing) }, - /* Used to enable proactive NAT traversal techniques */ - { "discover-binding", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN, - TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(TRUE), - G_STRUCT_OFFSET (TpsipConnParams, discover_binding) }, - /* Mechanism used for connection keepalive maintenance */ - { "keepalive-mechanism", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, - TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, "auto", - G_STRUCT_OFFSET (TpsipConnParams, keepalive_mechanism) }, - /* Keep-alive interval */ - { "keepalive-interval", DBUS_TYPE_UINT32_AS_STRING, G_TYPE_UINT, - TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(0), - G_STRUCT_OFFSET (TpsipConnParams, keepalive_interval) }, - /* Use SRV DNS lookup to discover STUN server */ - { "discover-stun", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN, - TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(TRUE), - G_STRUCT_OFFSET (TpsipConnParams, discover_stun) }, - /* STUN server */ - { "stun-server", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, - 0, NULL, G_STRUCT_OFFSET (TpsipConnParams, stun_server) }, - /* STUN port */ - { "stun-port", DBUS_TYPE_UINT16_AS_STRING, G_TYPE_UINT, - TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, - GUINT_TO_POINTER(TPSIP_DEFAULT_STUN_PORT), - G_STRUCT_OFFSET (TpsipConnParams, stun_port) }, - /* If the session content cannot be modified once initially set up */ - { "immutable-streams", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN, - TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(FALSE), - G_STRUCT_OFFSET (TpsipConnParams, immutable_streams) }, - /* Local IP address to use, workaround purposes only */ - { "local-ip-address", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, - 0, NULL, G_STRUCT_OFFSET (TpsipConnParams, local_ip_address) }, - /* Local port for SIP, workaround purposes only */ - { "local-port", DBUS_TYPE_UINT16_AS_STRING, G_TYPE_UINT, - 0, NULL, G_STRUCT_OFFSET (TpsipConnParams, local_port) }, - /* Extra-realm authentication */ - { "extra-auth-user", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, - 0, NULL, G_STRUCT_OFFSET (TpsipConnParams, extra_auth_user) }, - { "extra-auth-password", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, - TP_CONN_MGR_PARAM_FLAG_SECRET, - NULL, G_STRUCT_OFFSET (TpsipConnParams, extra_auth_password) }, - { NULL, NULL, 0, 0, NULL, 0 } -}; - -const TpCMProtocolSpec tpsip_protocols[] = { - { "sip", tpsip_params, alloc_params, free_params }, - { NULL, NULL } -}; - struct _TpsipConnectionManagerPrivate { su_root_t *sofia_root; @@ -237,10 +79,24 @@ tpsip_connection_manager_init (TpsipConnectionManager *obj) #endif } +static void +tpsip_connection_manager_constructed (GObject *object) +{ + TpsipConnectionManager *self = TPSIP_CONNECTION_MANAGER (object); + TpBaseConnectionManager *base = (TpBaseConnectionManager *) self; + void (*constructed) (GObject *) = + ((GObjectClass *) tpsip_connection_manager_parent_class)->constructed; + TpBaseProtocol *protocol; + + if (constructed != NULL) + constructed (object); + + protocol = tpsip_protocol_new (self->priv->sofia_root); + tp_base_connection_manager_add_protocol (base, protocol); + g_object_unref (protocol); +} + static void tpsip_connection_manager_finalize (GObject *object); -static TpBaseConnection *tpsip_connection_manager_new_connection ( - TpBaseConnectionManager *base, const gchar *proto, - TpIntSet *params_present, void *parsed_params, GError **error); static void tpsip_connection_manager_class_init (TpsipConnectionManagerClass *klass) @@ -251,11 +107,10 @@ tpsip_connection_manager_class_init (TpsipConnectionManagerClass *klass) g_type_class_add_private (klass, sizeof (TpsipConnectionManagerPrivate)); + object_class->constructed = tpsip_connection_manager_constructed; object_class->finalize = tpsip_connection_manager_finalize; - base_class->new_connection = tpsip_connection_manager_new_connection; base_class->cm_dbus_name = "sofiasip"; - base_class->protocol_params = tpsip_protocols; } void @@ -279,248 +134,3 @@ tpsip_connection_manager_finalize (GObject *object) G_OBJECT_CLASS (tpsip_connection_manager_parent_class)->finalize (object); } - -static gchar * -priv_compose_proxy_uri (const gchar *host, - const gchar *transport, - guint port) -{ - const gchar *scheme = "sip"; - - if (host == NULL) - return NULL; - - /* Set scheme to SIPS if transport is TLS */ - - if (transport != NULL && !g_ascii_strcasecmp (transport, "tls")) - scheme = "sips"; - - /* Format the resulting URI */ - - if (port) - return g_strdup_printf ("%s:%s:%u", scheme, host, port); - else - return g_strdup_printf ("%s:%s", scheme, host); -} - -/** - * Returns a default SIP proxy address based on the public - * SIP address 'sip_address' and . For instance - * "sip:first.surname@company.com" would result in "sip:company.com". - * The SIP stack will further utilize DNS lookups to find the IP address - * for the SIP server responsible for the domain "company.com". - */ -static gchar * -priv_compose_default_proxy_uri (const gchar *sip_address, - const gchar *transport) -{ - char *result = NULL; - char *host; - char *found; - - g_return_val_if_fail (sip_address != NULL, NULL); - - /* skip sip and sips prefixes, updating transport if necessary */ - found = strchr (sip_address, ':'); - if (found != NULL) { - if (g_ascii_strncasecmp ("sip:", sip_address, 4) == 0) - ; - else if (g_ascii_strncasecmp ("sips:", sip_address, 5) == 0) - { - if (transport == NULL || - strcmp (transport, "auto") == 0) - transport = "tls"; - } - else - { - /* error, unknown URI prefix */ - return NULL; - } - - sip_address = found + 1; - } - - /* skip userinfo */ - found = strchr (sip_address, '@'); - if (found != NULL) - sip_address = found + 1; - - /* copy rest of the string */ - host = g_strdup (sip_address); - - /* mark end (before uri-parameters defs or headers) */ - found = strchr (host, ';'); - if (found == NULL) - found = strchr (host, '?'); - if (found != NULL) - *found = '\0'; - - result = priv_compose_proxy_uri (host, transport, 0); - - g_free (host); - - return result; -} - -static TpsipConnectionKeepaliveMechanism -priv_parse_keepalive (const gchar *str) -{ - if (str == NULL || strcmp (str, "auto") == 0) - return TPSIP_CONNECTION_KEEPALIVE_AUTO; - if (strcmp (str, "register") == 0) - return TPSIP_CONNECTION_KEEPALIVE_REGISTER; - if (strcmp (str, "options") == 0) - return TPSIP_CONNECTION_KEEPALIVE_OPTIONS; - if (strcmp (str, "stun") == 0) - return TPSIP_CONNECTION_KEEPALIVE_STUN; - if (strcmp (str, "off") == 0) - return TPSIP_CONNECTION_KEEPALIVE_NONE; - - WARNING ("unsupported keepalive-method value \"%s\", falling back to auto", str); - return TPSIP_CONNECTION_KEEPALIVE_AUTO; -} - -#define SET_PROPERTY_IF_PARAM_SET(prop, param, member) \ - if (tp_intset_is_member (params_present, param)) \ - { \ - g_object_set (connection, prop, member, NULL); \ - } - -#define NULLIFY_IF_EMPTY(param) \ - if ((param) != NULL && (param)[0] == '\0') \ - param = NULL; - -static gboolean -check_not_empty_if_present (const gchar *name, - const gchar *value, - GError **error) -{ - if (value != NULL && value[0] == '\0') - { - g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, - "If supplied, '%s' account parameter may not be empty", name); - return FALSE; - } - return TRUE; -} - -static TpBaseConnection * -tpsip_connection_manager_new_connection (TpBaseConnectionManager *base, - const gchar *proto, - TpIntSet *params_present, - void *parsed_params, - GError **error) -{ - TpsipConnectionManager *self = TPSIP_CONNECTION_MANAGER (base); - TpsipConnectionManagerPrivate *priv = TPSIP_CONNECTION_MANAGER_GET_PRIVATE (self); - TpBaseConnection *connection = NULL; - TpsipConnParams *params = (TpsipConnParams *)parsed_params; - gchar *proxy = NULL; - TpsipConnectionKeepaliveMechanism keepalive_mechanism; - - if (strcmp (proto, "sip")) { - g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, - "This connection manager only implements protocol 'sip', not '%s'", - proto); - return NULL; - } - - /* TpBaseConnectionManager code has already checked that required params - * are present (but not that they are non-empty, if we're using >= 0.5.8) - */ - g_assert (params->account); - - /* FIXME: validate account SIP URI properly, using appropriate RFCs */ - if (!check_not_empty_if_present ("account", params->account, error)) - return FALSE; - NULLIFY_IF_EMPTY (params->auth_user); - NULLIFY_IF_EMPTY (params->password); - /* FIXME: validate registrar SIP URI properly, using appropriate RFCs */ - NULLIFY_IF_EMPTY (params->registrar); - /* FIXME: validate proxy host properly */ - NULLIFY_IF_EMPTY (params->proxy_host); - /* FIXME: check against the list (which presumably exists) of valid - * transports */ - NULLIFY_IF_EMPTY (params->transport); - /* FIXME: check against the list (which presumably exists) of valid - * KA mechanisms */ - NULLIFY_IF_EMPTY (params->keepalive_mechanism); - /* FIXME: validate STUN server properly */ - NULLIFY_IF_EMPTY (params->stun_server); - /* FIXME: validate local IP address properly */ - NULLIFY_IF_EMPTY (params->local_ip_address); - NULLIFY_IF_EMPTY (params->extra_auth_user); - NULLIFY_IF_EMPTY (params->extra_auth_password); - - DEBUG("New SIP connection to %s", params->account); - - connection = (TpBaseConnection *)g_object_new(TPSIP_TYPE_CONNECTION, - "protocol", "sip", - "sofia-root", priv->sofia_root, - "address", params->account, - NULL); - - if (params->proxy_host == NULL) { - proxy = priv_compose_default_proxy_uri (params->account, - params->transport); - DEBUG("set outbound proxy address to <%s>, based on <%s>", proxy, params->account); - } else - proxy = priv_compose_proxy_uri (params->proxy_host, - params->transport, - params->port); - - g_object_set (connection, "proxy", proxy, NULL); - g_free (proxy); - - if (params->transport != NULL && strcmp (params->transport, "auto") != 0) - g_object_set (connection, "transport", params->transport, NULL); - - SET_PROPERTY_IF_PARAM_SET ("auth-user", TPSIP_CONN_PARAM_AUTH_USER, - params->auth_user); - - SET_PROPERTY_IF_PARAM_SET ("password", TPSIP_CONN_PARAM_PASSWORD, - params->password); - - SET_PROPERTY_IF_PARAM_SET ("alias", TPSIP_CONN_PARAM_ALIAS, - params->alias); - - SET_PROPERTY_IF_PARAM_SET ("registrar", TPSIP_CONN_PARAM_REGISTRAR, - params->registrar); - - SET_PROPERTY_IF_PARAM_SET ("loose-routing", TPSIP_CONN_PARAM_LOOSE_ROUTING, - params->loose_routing); - - SET_PROPERTY_IF_PARAM_SET ("discover-binding", TPSIP_CONN_PARAM_DISCOVER_BINDING, - params->discover_binding); - - SET_PROPERTY_IF_PARAM_SET ("discover-stun", TPSIP_CONN_PARAM_DISCOVER_STUN, - params->discover_stun); - - SET_PROPERTY_IF_PARAM_SET ("stun-server", TPSIP_CONN_PARAM_STUN_SERVER, - params->stun_server); - - SET_PROPERTY_IF_PARAM_SET ("stun-port", TPSIP_CONN_PARAM_STUN_PORT, - params->stun_port); - - SET_PROPERTY_IF_PARAM_SET ("immutable-streams", TPSIP_CONN_PARAM_IMMUTABLE_STREAMS, - params->immutable_streams); - - SET_PROPERTY_IF_PARAM_SET ("keepalive-interval", - TPSIP_CONN_PARAM_KEEPALIVE_INTERVAL, params->keepalive_interval); - - keepalive_mechanism = priv_parse_keepalive (params->keepalive_mechanism); - g_object_set (connection, "keepalive-mechanism", keepalive_mechanism, NULL); - - SET_PROPERTY_IF_PARAM_SET ("local-ip-address", TPSIP_CONN_PARAM_LOCAL_IP_ADDRESS, - params->local_ip_address); - - SET_PROPERTY_IF_PARAM_SET ("local-port", TPSIP_CONN_PARAM_LOCAL_PORT, - params->local_port); - - SET_PROPERTY_IF_PARAM_SET ("extra-auth-user", TPSIP_CONN_PARAM_EXTRA_AUTH_USER, - params->extra_auth_user); - SET_PROPERTY_IF_PARAM_SET ("extra-auth-password", TPSIP_CONN_PARAM_EXTRA_AUTH_PASSWORD, - params->extra_auth_password); - - return connection; -} diff --git a/src/sip-connection-private.h b/src/sip-connection-private.h index 6a7efcf..2250694 100644 --- a/src/sip-connection-private.h +++ b/src/sip-connection-private.h @@ -27,6 +27,8 @@ #include <tpsip/sofia-decls.h> #include <sofia-sip/sresolv.h> +#include <telepathy-glib/simple-password-manager.h> + #ifdef HAVE_LIBIPHB #include <libiphb.h> #endif @@ -51,6 +53,7 @@ struct _TpsipConnectionPrivate gchar *registrar_realm; TpsipMediaFactory *media_factory; + TpSimplePasswordManager *password_manager; gchar *address; gchar *auth_user; diff --git a/src/sip-connection.c b/src/sip-connection.c index bc175cf..b515699 100644 --- a/src/sip-connection.c +++ b/src/sip-connection.c @@ -160,6 +160,10 @@ tpsip_connection_create_channel_managers (TpBaseConnection *conn) "connection", self, NULL); g_ptr_array_add (channel_managers, priv->media_factory); + priv->password_manager = tp_simple_password_manager_new ( + conn); + g_ptr_array_add (channel_managers, priv->password_manager); + return channel_managers; } @@ -422,15 +426,22 @@ static void tpsip_connection_shut_down (TpBaseConnection *base); static gboolean tpsip_connection_start_connecting (TpBaseConnection *base, GError **error); +static const gchar *interfaces_always_present[] = { + TP_IFACE_CONNECTION_INTERFACE_REQUESTS, + TP_IFACE_CONNECTION_INTERFACE_CONTACTS, + TP_IFACE_CONNECTION_INTERFACE_ALIASING, + NULL }; + +const gchar ** +tpsip_connection_get_implemented_interfaces (void) +{ + /* we don't have any conditionally-implemented interfaces */ + return interfaces_always_present; +} + static void tpsip_connection_class_init (TpsipConnectionClass *klass) { - static const gchar *interfaces_always_present[] = { - TP_IFACE_CONNECTION_INTERFACE_REQUESTS, - TP_IFACE_CONNECTION_INTERFACE_CONTACTS, - TP_IFACE_CONNECTION_INTERFACE_ALIASING, - NULL }; - GObjectClass *object_class = G_OBJECT_CLASS (klass); TpBaseConnectionClass *base_class = (TpBaseConnectionClass *)klass; @@ -593,6 +604,54 @@ tpsip_connection_class_init (TpsipConnectionClass *klass) G_STRUCT_OFFSET (TpsipConnectionClass, contacts_class)); } +typedef struct { + TpsipConnection* self; + nua_handle_t *nh; + gchar *method; + gchar *realm; + gchar *user; +} PrivHandleAuthData; + +static PrivHandleAuthData * +priv_handle_auth_data_new (TpsipConnection* self, + nua_handle_t *nh, + const gchar *method, + const gchar *realm, + const gchar *user) +{ + PrivHandleAuthData *data = g_slice_new (PrivHandleAuthData); + + data->self = g_object_ref (self); + data->nh = nua_handle_ref (nh); + data->method = g_strdup (method); + data->realm = g_strdup (realm); + data->user = g_strdup (user); + + return data; +} + +static void +priv_handle_auth_data_free (PrivHandleAuthData *data) +{ + g_object_unref (data->self); + nua_handle_unref (data->nh); + g_free (data->method); + g_free (data->realm); + g_free (data->user); + + g_slice_free (PrivHandleAuthData, data); +} + +static void priv_password_manager_prompt_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data); +static void priv_handle_auth_continue (TpsipConnection* self, + nua_handle_t *nh, + const gchar *method, + const gchar *realm, + const gchar *user, + const gchar *password); + static gboolean priv_handle_auth (TpsipConnection* self, int status, @@ -607,7 +666,6 @@ priv_handle_auth (TpsipConnection* self, const char *realm = NULL; const char *user = NULL; const char *password = NULL; - gchar *auth = NULL; if (status != 401 && status != 407) return FALSE; @@ -635,6 +693,12 @@ priv_handle_auth (TpsipConnection* self, return FALSE; } + if (method == NULL) + { + WARNING ("no method presented for authentication"); + return FALSE; + } + /* step: determine which set of credentials to use */ if (home_realm) { @@ -668,6 +732,9 @@ priv_handle_auth (TpsipConnection* self, /* fall back to the main username */ user = priv->auth_user; password = priv->extra_auth_password; + if (password == NULL) + /* note that this prevents asking the user for a password */ + password = ""; DEBUG("using the extra auth credentials"); } @@ -678,37 +745,101 @@ priv_handle_auth (TpsipConnection* self, if (sipfrom && sipfrom->a_url[0].url_user) /* or use the userpart in "From" header */ user = sipfrom->a_url[0].url_user; + else + return FALSE; } if (password == NULL) - password = ""; + { + PrivHandleAuthData *data = NULL; + + DEBUG("asking the user for a password."); + data = priv_handle_auth_data_new (self, nh, method, realm, + user); + tp_simple_password_manager_prompt_async (priv->password_manager, + priv_password_manager_prompt_cb, data); + /* Promise that we'll handle it eventually, even if we end up just + * handling it with a blank password. */ + return TRUE; + } - /* step: if all info is available, create an authorization response */ - g_assert (realm != NULL); - if (user && method) { - if (realm[0] == '"') - auth = g_strdup_printf ("%s:%s:%s:%s", - method, realm, user, password); - else - auth = g_strdup_printf ("%s:\"%s\":%s:%s", - method, realm, user, password); + priv_handle_auth_continue (self, nh, method, realm, + user, password); + return TRUE; +} - DEBUG("%s authenticating user='%s' realm=%s", - wa ? "server" : "proxy", user, realm); - } +static void +priv_password_manager_prompt_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + PrivHandleAuthData *data = user_data; + GError *error = NULL; + const GString *password_string; + const gchar *password; + + password_string = tp_simple_password_manager_prompt_finish ( + TP_SIMPLE_PASSWORD_MANAGER (source_object), result, &error); - if (auth == NULL) + if (error != NULL) { - WARNING ("authentication data are incomplete"); - return FALSE; + /* we promised to handle the auth challenge in priv_handle_auth() by + * returning TRUE, so we need to handle it anyway, even if it means + * doing it with a blank password. + */ + DEBUG ("Auth channel failed: %s. Using blank password.", error->message); + + password = ""; + + g_error_free (error); } + else + { + TpsipConnectionPrivate *priv = + TPSIP_CONNECTION_GET_PRIVATE (data->self); + + password = password_string->str; + /* also save it for later. */ + g_free (priv->password); + priv->password = g_strdup (password); + } + + priv_handle_auth_continue (data->self, data->nh, data->method, data->realm, + data->user, password); + + priv_handle_auth_data_free (data); +} + +static void +priv_handle_auth_continue (TpsipConnection* self, + nua_handle_t *nh, + const gchar *method, + const gchar *realm, + const gchar *user, + const gchar *password) +{ + gchar *auth = NULL; + + /* step: if all info is available, create an authorization response */ + g_assert (realm != NULL); + g_assert (method != NULL); + g_assert (user != NULL); + g_assert (password != NULL); + + if (realm[0] == '"') + auth = g_strdup_printf ("%s:%s:%s:%s", + method, realm, user, password); + else + auth = g_strdup_printf ("%s:\"%s\":%s:%s", + method, realm, user, password); + + DEBUG ("%s-authenticating user='%s' realm=%s", + method, user, realm); /* step: authenticate */ nua_authenticate(nh, NUTAG_AUTH(auth), TAG_END()); g_free (auth); - - return TRUE; } static gboolean diff --git a/src/sip-connection.h b/src/sip-connection.h index ac025ca..2280be9 100644 --- a/src/sip-connection.h +++ b/src/sip-connection.h @@ -80,6 +80,8 @@ GType tpsip_connection_get_type (void) G_GNUC_CONST; void tpsip_connection_connect_auth_handler (TpsipConnection *self, TpsipEventTarget *target); +const gchar **tpsip_connection_get_implemented_interfaces (void); + G_END_DECLS #endif /* #ifndef __TPSIP_CONNECTION_H__*/ diff --git a/src/sip-media-channel.c b/src/sip-media-channel.c index c6d6df5..8f896ed 100644 --- a/src/sip-media-channel.c +++ b/src/sip-media-channel.c @@ -189,7 +189,7 @@ tpsip_media_channel_constructed (GObject *obj) TpBaseConnection *conn = (TpBaseConnection *)(priv->conn); GObjectClass *parent_object_class = G_OBJECT_CLASS (tpsip_media_channel_parent_class); - DBusGConnection *bus; + TpDBusDaemon *bus; TpHandleRepoIface *contact_repo; TpIntSet *set; @@ -203,10 +203,10 @@ tpsip_media_channel_constructed (GObject *obj) tp_handle_ref (contact_repo, priv->handle); /* register object on the bus */ - bus = tp_get_bus (); + bus = tp_base_connection_get_dbus_daemon (conn); DEBUG("registering object to dbus path=%s", priv->object_path); - dbus_g_connection_register_g_object (bus, priv->object_path, obj); + tp_dbus_daemon_register_object (bus, priv->object_path, obj); /* initialize group mixin */ tp_group_mixin_init (obj, @@ -1622,6 +1622,8 @@ priv_create_session (TpsipMediaChannel *channel, NULL); session = g_object_new (TPSIP_TYPE_MEDIA_SESSION, + "dbus-daemon", + tp_base_connection_get_dbus_daemon (conn), "media-channel", channel, "object-path", object_path, "nua-handle", nh, diff --git a/src/sip-media-session.c b/src/sip-media-session.c index 73b7482..8272200 100644 --- a/src/sip-media-session.c +++ b/src/sip-media-session.c @@ -72,6 +72,7 @@ enum enum { PROP_MEDIA_CHANNEL = 1, + PROP_DBUS_DAEMON, PROP_OBJECT_PATH, PROP_NUA_OP, PROP_PEER, @@ -122,6 +123,7 @@ typedef struct _TpsipMediaSessionPrivate TpsipMediaSessionPrivate; struct _TpsipMediaSessionPrivate { + TpDBusDaemon *dbus_daemon; TpsipMediaChannel *channel; /* see gobj. prop. 'media-channel' */ gchar *object_path; /* see gobj. prop. 'object-path' */ nua_handle_t *nua_op; /* see gobj. prop. 'nua-handle' */ @@ -192,14 +194,13 @@ tpsip_media_session_constructor (GType type, guint n_props, { GObject *obj; TpsipMediaSessionPrivate *priv; - DBusGConnection *bus; obj = G_OBJECT_CLASS (tpsip_media_session_parent_class)-> constructor (type, n_props, props); priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (TPSIP_MEDIA_SESSION (obj)); - bus = tp_get_bus (); - dbus_g_connection_register_g_object (bus, priv->object_path, obj); + g_assert (TP_IS_DBUS_DAEMON (priv->dbus_daemon)); + tp_dbus_daemon_register_object (priv->dbus_daemon, priv->object_path, obj); return obj; } @@ -212,7 +213,11 @@ static void tpsip_media_session_get_property (GObject *object, TpsipMediaSession *session = TPSIP_MEDIA_SESSION (object); TpsipMediaSessionPrivate *priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (session); - switch (property_id) { + switch (property_id) + { + case PROP_DBUS_DAEMON: + g_value_set_object (value, priv->dbus_daemon); + break; case PROP_MEDIA_CHANNEL: g_value_set_object (value, priv->channel); break; @@ -296,7 +301,12 @@ static void tpsip_media_session_set_property (GObject *object, TpsipMediaSession *session = TPSIP_MEDIA_SESSION (object); TpsipMediaSessionPrivate *priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (session); - switch (property_id) { + switch (property_id) + { + case PROP_DBUS_DAEMON: + g_assert (priv->dbus_daemon == NULL); /* construct-only */ + priv->dbus_daemon = g_value_dup_object (value); + break; case PROP_MEDIA_CHANNEL: priv->channel = TPSIP_MEDIA_CHANNEL (g_value_get_object (value)); break; @@ -343,6 +353,11 @@ tpsip_media_session_class_init (TpsipMediaSessionClass *klass) object_class->dispose = tpsip_media_session_dispose; object_class->finalize = tpsip_media_session_finalize; + param_spec = g_param_spec_object ("dbus-daemon", "TpDBusDaemon", + "Connection to D-Bus.", TP_TYPE_DBUS_DAEMON, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_DBUS_DAEMON, param_spec); + param_spec = g_param_spec_object ("media-channel", "TpsipMediaChannel object", "SIP media channel object that owns this media session object" " (not reference counted).", @@ -442,6 +457,8 @@ tpsip_media_session_dispose (GObject *object) if (priv->glare_timer_id) g_source_remove (priv->glare_timer_id); + tp_clear_object (&priv->dbus_daemon); + if (G_OBJECT_CLASS (tpsip_media_session_parent_class)->dispose) G_OBJECT_CLASS (tpsip_media_session_parent_class)->dispose (object); @@ -2118,6 +2135,7 @@ tpsip_media_session_add_stream (TpsipMediaSession *self, direction &= ~TP_MEDIA_STREAM_DIRECTION_RECEIVE; stream = g_object_new (TPSIP_TYPE_MEDIA_STREAM, + "dbus-daemon", priv->dbus_daemon, "media-session", self, "media-type", media_type, "object-path", object_path, diff --git a/src/sip-media-stream.c b/src/sip-media-stream.c index 37e14df..d3826bb 100644 --- a/src/sip-media-stream.c +++ b/src/sip-media-stream.c @@ -84,6 +84,7 @@ static guint signals[SIG_LAST_SIGNAL] = {0}; enum { PROP_MEDIA_SESSION = 1, + PROP_DBUS_DAEMON, PROP_OBJECT_PATH, PROP_ID, PROP_MEDIA_TYPE, @@ -105,6 +106,7 @@ typedef struct _TpsipMediaStreamPrivate TpsipMediaStreamPrivate; struct _TpsipMediaStreamPrivate { + TpDBusDaemon *dbus_daemon; TpsipMediaSession *session; /* see gobj. prop. 'media-session' */ gchar *object_path; /* see gobj. prop. 'object-path' */ guint id; /* see gobj. prop. 'id' */ @@ -200,7 +202,6 @@ tpsip_media_stream_constructed (GObject *obj) TPSIP_MEDIA_STREAM (obj)); GObjectClass *parent_object_class = G_OBJECT_CLASS (tpsip_media_stream_parent_class); - DBusGConnection *bus; /* call base class method */ if (parent_object_class->constructed != NULL) @@ -220,8 +221,8 @@ tpsip_media_stream_constructed (GObject *obj) } /* go for the bus */ - bus = tp_get_bus (); - dbus_g_connection_register_g_object (bus, priv->object_path, obj); + g_assert (TP_IS_DBUS_DAEMON (priv->dbus_daemon)); + tp_dbus_daemon_register_object (priv->dbus_daemon, priv->object_path, obj); } static void @@ -235,6 +236,9 @@ tpsip_media_stream_get_property (GObject *object, switch (property_id) { + case PROP_DBUS_DAEMON: + g_value_set_object (value, priv->dbus_daemon); + break; case PROP_MEDIA_SESSION: g_value_set_object (value, priv->session); break; @@ -288,6 +292,10 @@ tpsip_media_stream_set_property (GObject *object, switch (property_id) { + case PROP_DBUS_DAEMON: + g_assert (priv->dbus_daemon == NULL); /* construct-only */ + priv->dbus_daemon = g_value_dup_object (value); + break; case PROP_MEDIA_SESSION: priv->session = g_value_get_object (value); break; @@ -358,6 +366,11 @@ tpsip_media_stream_class_init (TpsipMediaStreamClass *klass) object_class->dispose = tpsip_media_stream_dispose; object_class->finalize = tpsip_media_stream_finalize; + param_spec = g_param_spec_object ("dbus-daemon", "TpDBusDaemon", + "Connection to D-Bus.", TP_TYPE_DBUS_DAEMON, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_DBUS_DAEMON, param_spec); + param_spec = g_param_spec_object ("media-session", "TpsipMediaSession object", "SIP media session object that owns this media stream object.", TPSIP_TYPE_MEDIA_SESSION, @@ -516,7 +529,7 @@ tpsip_media_stream_dispose (GObject *object) priv->dispose_has_run = TRUE; - /* release any references held by the object here */ + tp_clear_object (&priv->dbus_daemon); if (G_OBJECT_CLASS (tpsip_media_stream_parent_class)->dispose) G_OBJECT_CLASS (tpsip_media_stream_parent_class)->dispose (object); diff --git a/src/sip-text-channel.c b/src/sip-text-channel.c index ec0ed32..aeb0209 100644 --- a/src/sip-text-channel.c +++ b/src/sip-text-channel.c @@ -162,7 +162,7 @@ tpsip_text_channel_constructed (GObject *obj) TpsipTextChannelPrivate *priv; TpBaseConnection *base_conn; TpHandleRepoIface *contact_handles; - DBusGConnection *bus; + TpDBusDaemon *bus; TpChannelTextMessageType types[] = { TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, }; @@ -202,8 +202,8 @@ tpsip_text_channel_constructed (GObject *obj) TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_SUCCESSES, supported_content_types); - bus = tp_get_bus(); - dbus_g_connection_register_g_object(bus, priv->object_path, obj); + bus = tp_base_connection_get_dbus_daemon (base_conn); + tp_dbus_daemon_register_object (bus, priv->object_path, obj); } diff --git a/src/text-factory.c b/src/text-factory.c index 36730de..d62b2b6 100644 --- a/src/text-factory.c +++ b/src/text-factory.c @@ -328,8 +328,8 @@ static const gchar * const text_channel_allowed_properties[] = { }; static void -tpsip_text_factory_foreach_channel_class (TpChannelManager *manager, - TpChannelManagerChannelClassFunc func, +tpsip_text_factory_type_foreach_channel_class (GType type, + TpChannelManagerTypeChannelClassFunc func, gpointer user_data) { GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal, @@ -346,7 +346,7 @@ tpsip_text_factory_foreach_channel_class (TpChannelManager *manager, g_hash_table_insert (table, (gchar *) text_channel_fixed_properties[1], value); - func (manager, table, text_channel_allowed_properties, user_data); + func (type, table, text_channel_allowed_properties, user_data); g_hash_table_destroy (table); } @@ -635,7 +635,8 @@ channel_manager_iface_init (gpointer g_iface, gpointer iface_data) TpChannelManagerIface *iface = g_iface; iface->foreach_channel = tpsip_text_factory_foreach_channel; - iface->foreach_channel_class = tpsip_text_factory_foreach_channel_class; + iface->type_foreach_channel_class = + tpsip_text_factory_type_foreach_channel_class; iface->create_channel = tpsip_text_factory_create_channel; iface->request_channel = tpsip_text_factory_request_channel; iface->ensure_channel = tpsip_text_factory_ensure_channel; diff --git a/src/write-mgr-file.c b/src/write-mgr-file.c index e718d91..402e89e 100644 --- a/src/write-mgr-file.c +++ b/src/write-mgr-file.c @@ -1,113 +1,357 @@ /* - * write_mgr_file.c - utility to produce .manager files. - * Copyright (C) 2006-2007 Collabora Ltd. - * Copyright (C) 2006-2007 Nokia Corporation + * write_mgr_file.c - utility to produce manager files. Derived from Gabble. + * Copyright (C) 2006-2010 Collabora Ltd. + * Copyright (C) 2006-2010 Nokia Corporation * - * This work is free software; you can redistribute it and/or + * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * - * This work is distributed in the hope that it will be useful, + * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this work; if not, write to the Free Software + * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "config.h" + #include <stdio.h> +#include <string.h> #include <dbus/dbus-glib.h> #include <dbus/dbus-protocol.h> +#include <telepathy-glib/telepathy-glib.h> -#include <telepathy-glib/enums.h> -#include "sip-connection-manager.h" +#include "protocol.h" -/* Cloned straight from Gabble - this might eventually go in a library */ -static gchar * -mgr_file_contents (const char *busname, - const char *objpath, - const TpCMProtocolSpec protocols[], - GError **error) + +#define WRITE_STR(prop, key) \ + { \ + const gchar *val = tp_asv_get_string (props, prop); \ + g_assert (!tp_str_empty (val)); \ + g_key_file_set_string (f, section_name, key, val); \ + } + +static void +write_parameters (GKeyFile *f, gchar *section_name, TpBaseProtocol *protocol) { - GKeyFile *f = g_key_file_new(); - const TpCMProtocolSpec *protocol; + const TpCMParamSpec *parameters = + tp_base_protocol_get_parameters (protocol); const TpCMParamSpec *row; - g_key_file_set_string(f, "ConnectionManager", "BusName", busname); - g_key_file_set_string(f, "ConnectionManager", "ObjectPath", objpath); + for (row = parameters; row->name; row++) + { + gchar *param_name = g_strdup_printf ("param-%s", row->name); + gchar *param_value = g_strdup_printf ("%s%s%s%s", row->dtype, + (row->flags & TP_CONN_MGR_PARAM_FLAG_REQUIRED ? " required" : ""), + (row->flags & TP_CONN_MGR_PARAM_FLAG_REGISTER ? " register" : ""), + (row->flags & TP_CONN_MGR_PARAM_FLAG_SECRET ? " secret" : "")); + g_key_file_set_string (f, section_name, param_name, param_value); + g_free (param_value); + g_free (param_name); + } - for (protocol = protocols; protocol->name; protocol++) + for (row = parameters; row->name; row++) { - gchar *section_name = g_strdup_printf("Protocol %s", protocol->name); + if (row->flags & TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT) + { + gchar *default_name = g_strdup_printf ("default-%s", row->name); - for (row = protocol->parameters; row->name; row++) + switch (row->gtype) + { + case G_TYPE_STRING: + g_key_file_set_string (f, section_name, default_name, + row->def); + break; + case G_TYPE_INT: + case G_TYPE_UINT: + g_key_file_set_integer (f, section_name, default_name, + GPOINTER_TO_INT(row->def)); + break; + case G_TYPE_BOOLEAN: + g_key_file_set_boolean (f, section_name, default_name, + GPOINTER_TO_INT(row->def) ? 1 : 0); + break; + default: + /* can't be in the case because G_TYPE_STRV is actually a + * function */ + if (row->gtype == G_TYPE_STRV) + { + g_key_file_set_string_list (f, section_name, default_name, + (const gchar **) row->def, + g_strv_length ((gchar **) row->def)); + } + } + g_free (default_name); + } + } +} + +static void +write_rcc_property (GKeyFile *keyfile, + const gchar *group_name, + const gchar *key, + GValue *val) +{ + switch (G_VALUE_TYPE (val)) + { + case G_TYPE_BOOLEAN: { - gchar *param_name = g_strdup_printf("param-%s", row->name); - gchar *param_value = g_strdup_printf("%s%s%s%s", row->dtype, - (row->flags & TP_CONN_MGR_PARAM_FLAG_REQUIRED ? " required" : ""), - (row->flags & TP_CONN_MGR_PARAM_FLAG_REGISTER ? " register" : ""), - (row->flags & TP_CONN_MGR_PARAM_FLAG_SECRET ? " secret" : "")); - g_key_file_set_string(f, section_name, param_name, param_value); - g_free(param_value); - g_free(param_name); + gchar *kf_key = g_strconcat (key, + " " DBUS_TYPE_BOOLEAN_AS_STRING, NULL); + g_key_file_set_boolean (keyfile, group_name, kf_key, + g_value_get_boolean (val)); + g_free (kf_key); + break; } - for (row = protocol->parameters; row->name; row++) + case G_TYPE_STRING: { - if (row->flags & TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT) - { - gchar *default_name = g_strdup_printf("default-%s", row->name); + gchar *kf_key = g_strconcat (key, + " " DBUS_TYPE_STRING_AS_STRING, NULL); + g_key_file_set_string (keyfile, group_name, kf_key, + g_value_get_string (val)); + g_free (kf_key); + break; + } - switch (row->gtype) - { - case G_TYPE_STRING: - g_key_file_set_string(f, section_name, default_name, - row->def); - break; - case G_TYPE_INT: - case G_TYPE_UINT: - g_key_file_set_integer(f, section_name, default_name, - GPOINTER_TO_INT(row->def)); - break; - case G_TYPE_BOOLEAN: - g_key_file_set_boolean(f, section_name, default_name, - GPOINTER_TO_INT(row->def) ? 1 : 0); - } - g_free(default_name); + case G_TYPE_UINT: + { + gchar *kf_key = g_strconcat (key, + " " DBUS_TYPE_UINT32_AS_STRING, NULL); + gchar *kf_val = g_strdup_printf ("%u", g_value_get_uint (val)); + g_key_file_set_value (keyfile, group_name, kf_key, kf_val); + g_free (kf_key); + g_free (kf_val); + break; + } + + /* FIXME: when we depend on Glib 2.26, we can use + * g_key_file_set_[u]int64 (g.o #614864). */ + case G_TYPE_UINT64: + { + gchar *kf_key = g_strconcat (key, + " " DBUS_TYPE_UINT64_AS_STRING, NULL); + gchar *kf_val = g_strdup_printf ("%" G_GUINT64_FORMAT, + g_value_get_uint64 (val)); + g_key_file_set_value (keyfile, group_name, kf_key, kf_val); + g_free (kf_key); + g_free (kf_val); + break; + } + + case G_TYPE_INT: + { + gchar *kf_key = g_strconcat (key, + " " DBUS_TYPE_INT32_AS_STRING, NULL); + g_key_file_set_integer (keyfile, group_name, kf_key, + g_value_get_int (val)); + g_free (kf_key); + break; + } + + case G_TYPE_INT64: + { + gchar *kf_key = g_strconcat (key, + " " DBUS_TYPE_UINT64_AS_STRING, NULL); + gchar *kf_val = g_strdup_printf ("%" G_GINT64_FORMAT, + g_value_get_int64 (val)); + g_key_file_set_value (keyfile, group_name, kf_key, kf_val); + g_free (kf_key); + g_free (kf_val); + break; + } + + default: + { + if (G_VALUE_TYPE (val) == G_TYPE_STRV) + { + gchar **list = g_value_get_boxed (val); + gchar *kf_key = g_strconcat (key, " " + DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING, NULL); + g_key_file_set_string_list (keyfile, group_name, + kf_key, (const gchar **) list, g_strv_length (list)); + g_free (kf_key); + break; } + + /* we'd rather crash than forget to write required rcc property */ + g_assert_not_reached (); } - g_free(section_name); } - return g_key_file_to_data(f, NULL, error); } -/* defined by telepathy-glib if your version is new enough - included here - * so telepathy-glib 0.5.4 and 0.5.5 will still work - */ -#ifndef TP_CM_BUS_NAME_BASE -# define TP_CM_BUS_NAME_BASE "org.freedesktop.Telepathy.ConnectionManager." -# define TP_CM_OBJECT_PATH_BASE "/org/freedesktop/Telepathy/ConnectionManager/" -#endif +static gchar * +generate_group_name (GHashTable *props) +{ + static guint counter = 0; + gchar *retval; + gchar *chan_type = g_ascii_strdown (tp_asv_get_string (props, + TP_PROP_CHANNEL_CHANNEL_TYPE), -1); + gchar *chan_type_suffix; + gchar *handle_type_name; + guint handle_type = tp_asv_get_uint32 (props, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL); + + g_assert (chan_type != NULL); + chan_type_suffix = strrchr (chan_type, '.'); + g_assert (chan_type_suffix != NULL); + chan_type_suffix++; + + switch (handle_type) + { + case TP_HANDLE_TYPE_CONTACT: + handle_type_name = "-1on1"; + break; + + case TP_HANDLE_TYPE_ROOM: + handle_type_name = "-multi"; + break; + + case TP_HANDLE_TYPE_GROUP: + handle_type_name = "-group"; + break; + + case TP_HANDLE_TYPE_LIST: + handle_type_name = "-list"; + break; + + default: + handle_type_name = ""; + } + + retval = g_strdup_printf ("%s%s-%d", chan_type_suffix, handle_type_name, + ++counter); + + g_free (chan_type); + return retval; +} + +static void +write_rccs (GKeyFile *f, const gchar *section_name, GHashTable *props) +{ + GPtrArray *rcc_list = tp_asv_get_boxed (props, + TP_PROP_PROTOCOL_REQUESTABLE_CHANNEL_CLASSES, + TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST); + guint i; + gchar **group_names = g_new0 (gchar *, rcc_list->len + 1); + + for (i = 0; i < rcc_list->len; i++) + { + gchar **allowed; + gchar *group_name; + GHashTable *fixed; + GHashTableIter iter; + gpointer k, v; + + tp_value_array_unpack (g_ptr_array_index (rcc_list, i), 2, + &fixed, &allowed); + + group_name = generate_group_name (fixed); + + g_hash_table_iter_init (&iter, fixed); + while (g_hash_table_iter_next (&iter, &k, &v)) + { + const gchar *key = k; + GValue *val = v; + + write_rcc_property (f, group_name, key, val); + } + + /* takes ownership */ + group_names[i] = group_name; + + g_key_file_set_string_list (f, group_name, "allowed", + (const gchar **) allowed, g_strv_length (allowed)); + } + + g_key_file_set_string_list (f, section_name, "RequestableChannelClasses", + (const gchar **) group_names, rcc_list->len); + + g_strfreev (group_names); +} + +static gchar * +mgr_file_contents (const char *busname, + const char *objpath, + GSList *protocols, + GError **error) +{ + GKeyFile *f = g_key_file_new (); + gchar *file_data; + + g_key_file_set_string (f, "ConnectionManager", "BusName", busname); + g_key_file_set_string (f, "ConnectionManager", "ObjectPath", objpath); + + /* there are no CM interfaces defined yet, so we cheat */ + g_key_file_set_string (f, "ConnectionManager", "Interfaces", ""); + + while (protocols != NULL) + { + TpBaseProtocol *protocol = protocols->data; + GHashTable *props = + tp_base_protocol_get_immutable_properties (protocol); + gchar *section_name = g_strdup_printf ("Protocol %s", + tp_base_protocol_get_name (protocol)); + const gchar * const *ifaces = tp_asv_get_strv (props, + TP_PROP_PROTOCOL_INTERFACES); + const gchar * const *c_ifaces = tp_asv_get_strv (props, + TP_PROP_PROTOCOL_CONNECTION_INTERFACES); + + write_parameters (f, section_name, protocol); + write_rccs (f, section_name, props); + + g_key_file_set_string_list (f, section_name, "Interfaces", + ifaces, g_strv_length ((gchar **) ifaces)); + g_key_file_set_string_list (f, section_name, "ConnectionInterfaces", + c_ifaces, g_strv_length ((gchar **) c_ifaces)); + + WRITE_STR (TP_PROP_PROTOCOL_VCARD_FIELD, "VCardField"); + WRITE_STR (TP_PROP_PROTOCOL_ENGLISH_NAME, "EnglishName"); + WRITE_STR (TP_PROP_PROTOCOL_ICON, "Icon"); + + g_free (section_name); + g_hash_table_destroy (props); + protocols = protocols->next; + } + + file_data = g_key_file_to_data (f, NULL, error); + g_key_file_free (f); + return file_data; +} int main (void) { GError *error = NULL; + gchar *s; + GSList *protocols = NULL; + + g_type_init (); + dbus_g_type_specialized_init (); + + protocols = g_slist_prepend (protocols, + tpsip_protocol_new (NULL)); + + s = mgr_file_contents (TP_CM_BUS_NAME_BASE "sofiasip", + TP_CM_OBJECT_PATH_BASE "sofiasip", + protocols, &error); + + g_object_unref (protocols->data); + g_slist_free (protocols); - gchar *s = mgr_file_contents(TP_CM_BUS_NAME_BASE "sofiasip", - TP_CM_OBJECT_PATH_BASE "sofiasip", - tpsip_protocols, &error); if (!s) { - fprintf(stderr, "%s\n", error->message); - g_error_free(error); + fprintf (stderr, "%s", error->message); + g_error_free (error); return 1; } - printf("%s", s); - g_free(s); + printf ("%s", s); + g_free (s); return 0; } diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am index 3b5e483..086e437 100644 --- a/tests/twisted/Makefile.am +++ b/tests/twisted/Makefile.am @@ -1,6 +1,8 @@ TWISTED_TESTS = \ + cm/protocol.py \ test-register.py \ test-register-fail.py \ + test-register-sasl.py \ test-handle-normalisation.py \ test-message.py \ test-self-alias.py \ @@ -27,6 +29,7 @@ check-twisted: EXTRA_DIST = \ $(TWISTED_TESTS) \ + constants.py \ sofiatest.py \ servicetest.py diff --git a/tests/twisted/cm/protocol.py b/tests/twisted/cm/protocol.py new file mode 100644 index 0000000..f0b0c40 --- /dev/null +++ b/tests/twisted/cm/protocol.py @@ -0,0 +1,58 @@ +""" +Test tpsip's o.fd.T.Protocol implementation +""" + +import dbus +from servicetest import (unwrap, tp_path_prefix, assertEquals, assertContains, + call_async) +from sofiatest import exec_test +import constants as cs + +def test(q, bus, conn, sip): + cm = bus.get_object(cs.CM + '.sofiasip', + tp_path_prefix + '/ConnectionManager/sofiasip') + cm_iface = dbus.Interface(cm, cs.CM) + cm_prop_iface = dbus.Interface(cm, cs.PROPERTIES_IFACE) + + protocols = unwrap(cm_prop_iface.Get(cs.CM, 'Protocols')) + assertEquals(set(['sip']), set(protocols.keys())) + + protocol_names = unwrap(cm_iface.ListProtocols()) + assertEquals(set(['sip']), set(protocol_names)) + + cm_params = cm_iface.GetParameters('sip') + local_props = protocols['sip'] + local_params = local_props[cs.PROTOCOL + '.Parameters'] + assertEquals(cm_params, local_params) + + proto = bus.get_object(cm.bus_name, cm.object_path + '/sip') + proto_iface = dbus.Interface(proto, cs.PROTOCOL) + proto_prop_iface = dbus.Interface(proto, cs.PROPERTIES_IFACE) + proto_props = unwrap(proto_prop_iface.GetAll(cs.PROTOCOL)) + + for key in ['Parameters', 'Interfaces', 'ConnectionInterfaces', + 'RequestableChannelClasses', u'VCardField', u'EnglishName', u'Icon']: + a = local_props[cs.PROTOCOL + '.' + key] + b = proto_props[key] + assertEquals(a, b) + + assertEquals('x-sip', proto_props['VCardField']) + assertEquals('SIP', proto_props['EnglishName']) + assertEquals('im-sip', proto_props['Icon']) + + assertContains(cs.CONN_IFACE_ALIASING, proto_props['ConnectionInterfaces']) + assertContains(cs.CONN_IFACE_CONTACTS, proto_props['ConnectionInterfaces']) + assertContains(cs.CONN_IFACE_REQUESTS, proto_props['ConnectionInterfaces']) + + assertEquals('sip:example@mit.edu', + unwrap(proto_iface.NormalizeContact('example@MIT.Edu'))) + + # Only account is mandatory + call_async(q, proto_iface, 'IdentifyAccount', {}) + q.expect('dbus-error', method='IdentifyAccount', name=cs.INVALID_ARGUMENT) + test_params = {'account': 'smcv@example.com'} + acc_name = unwrap(proto_iface.IdentifyAccount(test_params)) + assertEquals('smcv@example.com', acc_name) + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/constants.py b/tests/twisted/constants.py new file mode 100644 index 0000000..1ffa4d8 --- /dev/null +++ b/tests/twisted/constants.py @@ -0,0 +1,400 @@ +""" +Some handy constants for other tests to share and enjoy. +""" + +from dbus import PROPERTIES_IFACE + +CM = "org.freedesktop.Telepathy.ConnectionManager" + +HT_NONE = 0 +HT_CONTACT = 1 +HT_ROOM = 2 +HT_LIST = 3 +HT_GROUP = 4 + +CHANNEL = "org.freedesktop.Telepathy.Channel" + +CHANNEL_IFACE_CALL_STATE = CHANNEL + ".Interface.CallState" +CHANNEL_IFACE_CHAT_STATE = CHANNEL + '.Interface.ChatState' +CHANNEL_IFACE_DESTROYABLE = CHANNEL + ".Interface.Destroyable" +CHANNEL_IFACE_GROUP = CHANNEL + ".Interface.Group" +CHANNEL_IFACE_HOLD = CHANNEL + ".Interface.Hold" +CHANNEL_IFACE_MEDIA_SIGNALLING = CHANNEL + ".Interface.MediaSignalling" +CHANNEL_IFACE_MESSAGES = CHANNEL + ".Interface.Messages" +CHANNEL_IFACE_PASSWORD = CHANNEL + ".Interface.Password" +CHANNEL_IFACE_TUBE = CHANNEL + ".Interface.Tube" +CHANNEL_IFACE_SASL_AUTH = CHANNEL + ".Interface.SaslAuthentication.DRAFT" +CHANNEL_IFACE_CONFERENCE = CHANNEL + '.Interface.Conference' + +CHANNEL_TYPE_CALL = CHANNEL + ".Type.Call.DRAFT" +CHANNEL_TYPE_CONTACT_LIST = CHANNEL + ".Type.ContactList" +CHANNEL_TYPE_CONTACT_SEARCH = CHANNEL + ".Type.ContactSearch" +CHANNEL_TYPE_TEXT = CHANNEL + ".Type.Text" +CHANNEL_TYPE_TUBES = CHANNEL + ".Type.Tubes" +CHANNEL_TYPE_STREAM_TUBE = CHANNEL + ".Type.StreamTube" +CHANNEL_TYPE_DBUS_TUBE = CHANNEL + ".Type.DBusTube" +CHANNEL_TYPE_STREAMED_MEDIA = CHANNEL + ".Type.StreamedMedia" +CHANNEL_TYPE_TEXT = CHANNEL + ".Type.Text" +CHANNEL_TYPE_FILE_TRANSFER = CHANNEL + ".Type.FileTransfer" +CHANNEL_TYPE_SERVER_AUTHENTICATION = \ + CHANNEL + ".Type.ServerAuthentication.DRAFT" +CHANNEL_TYPE_SERVER_TLS_CONNECTION = \ + CHANNEL + ".Type.ServerTLSConnection" + +TP_AWKWARD_PROPERTIES = "org.freedesktop.Telepathy.Properties" +PROPERTY_FLAG_READ = 1 +PROPERTY_FLAG_WRITE = 2 +PROPERTY_FLAGS_RW = PROPERTY_FLAG_READ | PROPERTY_FLAG_WRITE + +CHANNEL_TYPE = CHANNEL + '.ChannelType' +TARGET_HANDLE_TYPE = CHANNEL + '.TargetHandleType' +TARGET_HANDLE = CHANNEL + '.TargetHandle' +TARGET_ID = CHANNEL + '.TargetID' +REQUESTED = CHANNEL + '.Requested' +INITIATOR_HANDLE = CHANNEL + '.InitiatorHandle' +INITIATOR_ID = CHANNEL + '.InitiatorID' +INTERFACES = CHANNEL + '.Interfaces' + +INITIAL_AUDIO = CHANNEL_TYPE_STREAMED_MEDIA + '.InitialAudio' +INITIAL_VIDEO = CHANNEL_TYPE_STREAMED_MEDIA + '.InitialVideo' +IMMUTABLE_STREAMS = CHANNEL_TYPE_STREAMED_MEDIA + '.ImmutableStreams' + +CALL_INITIAL_AUDIO = CHANNEL_TYPE_CALL + '.InitialAudio' +CALL_INITIAL_VIDEO = CHANNEL_TYPE_CALL + '.InitialVideo' +CALL_MUTABLE_CONTENTS = CHANNEL_TYPE_CALL + '.MutableContents' + +CALL_CONTENT = 'org.freedesktop.Telepathy.Call.Content.DRAFT' +CALL_CONTENT_IFACE_MEDIA = \ + 'org.freedesktop.Telepathy.Call.Content.Interface.Media.DRAFT' + +CALL_CONTENT_CODECOFFER = \ + 'org.freedesktop.Telepathy.Call.Content.CodecOffer.DRAFT' + +CALL_STREAM = 'org.freedesktop.Telepathy.Call.Stream.DRAFT' +CALL_STREAM_IFACE_MEDIA = \ + 'org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT' + +CALL_STREAM_ENDPOINT = 'org.freedesktop.Telepathy.Call.Stream.Endpoint.DRAFT' + +CALL_MEDIA_TYPE_AUDIO = 0 +CALL_MEDIA_TYPE_VIDEO = 1 + +CALL_STREAM_TRANSPORT_RAW_UDP = 0 +CALL_STREAM_TRANSPORT_ICE = 1 +CALL_STREAM_TRANSPORT_GOOGLE = 2 + +CALL_STATE_UNKNOWN = 0 +CALL_STATE_PENDING_INITIATOR = 1 +CALL_STATE_PENDING_RECEIVER = 2 +CALL_STATE_ACCEPTED = 3 +CALL_STATE_ENDED = 4 + +CALL_MEMBER_FLAG_RINGING = 1 +CALL_MEMBER_FLAG_HELD = 2 + +CALL_DISPOSITION_NONE = 0 +CALL_DISPOSITION_EARLY_MEDIA = 1 +CALL_DISPOSITION_INITIAL = 2 + +CALL_SENDING_STATE_NONE = 0 +CALL_SENDING_STATE_PENDING_SEND = 1 +CALL_SENDING_STATE_SENDING = 2 + +SUBSCRIPTION_STATE_UNKNOWN = 0 +SUBSCRIPTION_STATE_NO = 1 +SUBSCRIPTION_STATE_REMOVED_REMOTELY = 2 +SUBSCRIPTION_STATE_ASK = 3 +SUBSCRIPTION_STATE_YES = 4 + +CONTACT_LIST_STATE_NONE = 0 +CONTACT_LIST_STATE_WAITING = 1 +CONTACT_LIST_STATE_FAILURE = 2 +CONTACT_LIST_STATE_SUCCESS = 3 + +CONN = "org.freedesktop.Telepathy.Connection" +CONN_IFACE_AVATARS = CONN + '.Interface.Avatars' +CONN_IFACE_ALIASING = CONN + '.Interface.Aliasing' +CONN_IFACE_CAPS = CONN + '.Interface.Capabilities' +CONN_IFACE_CONTACTS = CONN + '.Interface.Contacts' +CONN_IFACE_CONTACT_CAPS = CONN + '.Interface.ContactCapabilities.DRAFT' +CONN_IFACE_CONTACT_INFO = CONN + ".Interface.ContactInfo" +CONN_IFACE_PRESENCE = CONN + '.Interface.Presence' +CONN_IFACE_SIMPLE_PRESENCE = CONN + '.Interface.SimplePresence' +CONN_IFACE_REQUESTS = CONN + '.Interface.Requests' +CONN_IFACE_LOCATION = CONN + '.Interface.Location' +CONN_IFACE_GABBLE_DECLOAK = CONN + '.Interface.Gabble.Decloak' +CONN_IFACE_MAIL_NOTIFICATION = CONN + '.Interface.MailNotification.DRAFT' +CONN_IFACE_CONTACT_LIST = CONN + '.Interface.ContactList' +CONN_IFACE_CONTACT_GROUPS = CONN + '.Interface.ContactGroups' + +ATTR_CONTACT_CAPABILITIES = CONN_IFACE_CONTACT_CAPS + '/capabilities' + +STREAM_HANDLER = 'org.freedesktop.Telepathy.Media.StreamHandler' + +ERROR = 'org.freedesktop.Telepathy.Error' +INVALID_ARGUMENT = ERROR + '.InvalidArgument' +NOT_IMPLEMENTED = ERROR + '.NotImplemented' +NOT_AVAILABLE = ERROR + '.NotAvailable' +PERMISSION_DENIED = ERROR + '.PermissionDenied' +OFFLINE = ERROR + '.Offline' +NOT_CAPABLE = ERROR + '.NotCapable' +CONNECTION_REFUSED = ERROR + '.ConnectionRefused' +CONNECTION_FAILED = ERROR + '.ConnectionFailed' +CONNECTION_LOST = ERROR + '.ConnectionLost' +CANCELLED = ERROR + '.Cancelled' +DISCONNECTED = ERROR + '.Disconnected' +REGISTRATION_EXISTS = ERROR + '.RegistrationExists' +AUTHENTICATION_FAILED = ERROR + '.AuthenticationFailed' +CONNECTION_REPLACED = ERROR + '.ConnectionReplaced' +ALREADY_CONNECTED = ERROR + '.AlreadyConnected' +NETWORK_ERROR = ERROR + '.NetworkError' +NOT_YET = ERROR + '.NotYet' +INVALID_HANDLE = ERROR + '.InvalidHandle' +CERT_UNTRUSTED = ERROR + '.Cert.Untrusted' + +UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod' + +TUBE_PARAMETERS = CHANNEL_IFACE_TUBE + '.Parameters' +TUBE_STATE = CHANNEL_IFACE_TUBE + '.State' +STREAM_TUBE_SERVICE = CHANNEL_TYPE_STREAM_TUBE + '.Service' +DBUS_TUBE_SERVICE_NAME = CHANNEL_TYPE_DBUS_TUBE + '.ServiceName' +DBUS_TUBE_DBUS_NAMES = CHANNEL_TYPE_DBUS_TUBE + '.DBusNames' +DBUS_TUBE_SUPPORTED_ACCESS_CONTROLS = CHANNEL_TYPE_DBUS_TUBE + '.SupportedAccessControls' +STREAM_TUBE_SUPPORTED_SOCKET_TYPES = CHANNEL_TYPE_STREAM_TUBE + '.SupportedSocketTypes' + +CONFERENCE_INITIAL_CHANNELS = CHANNEL_IFACE_CONFERENCE + '.InitialChannels' +CONFERENCE_INITIAL_INVITEE_HANDLES = CHANNEL_IFACE_CONFERENCE + '.InitialInviteeHandles' +CONFERENCE_INITIAL_INVITEE_IDS = CHANNEL_IFACE_CONFERENCE + '.InitialInviteeIDs' + +CONTACT_SEARCH_ASK = CHANNEL_TYPE_CONTACT_SEARCH + '.AvailableSearchKeys' +CONTACT_SEARCH_SERVER = CHANNEL_TYPE_CONTACT_SEARCH + '.Server' +CONTACT_SEARCH_STATE = CHANNEL_TYPE_CONTACT_SEARCH + '.SearchState' + +SEARCH_NOT_STARTED = 0 +SEARCH_IN_PROGRESS = 1 +SEARCH_MORE_AVAILABLE = 2 +SEARCH_COMPLETED = 3 +SEARCH_FAILED = 4 + +TUBE_CHANNEL_STATE_LOCAL_PENDING = 0 +TUBE_CHANNEL_STATE_REMOTE_PENDING = 1 +TUBE_CHANNEL_STATE_OPEN = 2 +TUBE_CHANNEL_STATE_NOT_OFFERED = 3 + +MEDIA_STREAM_TYPE_AUDIO = 0 +MEDIA_STREAM_TYPE_VIDEO = 1 + +SOCKET_ADDRESS_TYPE_UNIX = 0 +SOCKET_ADDRESS_TYPE_ABSTRACT_UNIX = 1 +SOCKET_ADDRESS_TYPE_IPV4 = 2 +SOCKET_ADDRESS_TYPE_IPV6 = 3 + +SOCKET_ACCESS_CONTROL_LOCALHOST = 0 +SOCKET_ACCESS_CONTROL_PORT = 1 +SOCKET_ACCESS_CONTROL_NETMASK = 2 +SOCKET_ACCESS_CONTROL_CREDENTIALS = 3 + +TUBE_STATE_LOCAL_PENDING = 0 +TUBE_STATE_REMOTE_PENDING = 1 +TUBE_STATE_OPEN = 2 +TUBE_STATE_NOT_OFFERED = 3 + +TUBE_TYPE_DBUS = 0 +TUBE_TYPE_STREAM = 1 + +MEDIA_STREAM_DIRECTION_NONE = 0 +MEDIA_STREAM_DIRECTION_SEND = 1 +MEDIA_STREAM_DIRECTION_RECEIVE = 2 +MEDIA_STREAM_DIRECTION_BIDIRECTIONAL = 3 + +MEDIA_STREAM_PENDING_LOCAL_SEND = 1 +MEDIA_STREAM_PENDING_REMOTE_SEND = 2 + +MEDIA_STREAM_TYPE_AUDIO = 0 +MEDIA_STREAM_TYPE_VIDEO = 1 + +MEDIA_STREAM_STATE_DISCONNECTED = 0 +MEDIA_STREAM_STATE_CONNECTING = 1 +MEDIA_STREAM_STATE_CONNECTED = 2 + +MEDIA_STREAM_DIRECTION_NONE = 0 +MEDIA_STREAM_DIRECTION_SEND = 1 +MEDIA_STREAM_DIRECTION_RECEIVE = 2 +MEDIA_STREAM_DIRECTION_BIDIRECTIONAL = 3 + +FT_STATE_NONE = 0 +FT_STATE_PENDING = 1 +FT_STATE_ACCEPTED = 2 +FT_STATE_OPEN = 3 +FT_STATE_COMPLETED = 4 +FT_STATE_CANCELLED = 5 + +FT_STATE_CHANGE_REASON_NONE = 0 +FT_STATE_CHANGE_REASON_REQUESTED = 1 +FT_STATE_CHANGE_REASON_LOCAL_STOPPED = 2 +FT_STATE_CHANGE_REASON_REMOTE_STOPPED = 3 +FT_STATE_CHANGE_REASON_LOCAL_ERROR = 4 +FT_STATE_CHANGE_REASON_REMOTE_ERROR = 5 + +FILE_HASH_TYPE_NONE = 0 +FILE_HASH_TYPE_MD5 = 1 +FILE_HASH_TYPE_SHA1 = 2 +FILE_HASH_TYPE_SHA256 = 3 + +FT_STATE = CHANNEL_TYPE_FILE_TRANSFER + '.State' +FT_CONTENT_TYPE = CHANNEL_TYPE_FILE_TRANSFER + '.ContentType' +FT_FILENAME = CHANNEL_TYPE_FILE_TRANSFER + '.Filename' +FT_SIZE = CHANNEL_TYPE_FILE_TRANSFER + '.Size' +FT_CONTENT_HASH_TYPE = CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType' +FT_CONTENT_HASH = CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash' +FT_DESCRIPTION = CHANNEL_TYPE_FILE_TRANSFER + '.Description' +FT_DATE = CHANNEL_TYPE_FILE_TRANSFER + '.Date' +FT_AVAILABLE_SOCKET_TYPES = CHANNEL_TYPE_FILE_TRANSFER + '.AvailableSocketTypes' +FT_TRANSFERRED_BYTES = CHANNEL_TYPE_FILE_TRANSFER + '.TransferredBytes' +FT_INITIAL_OFFSET = CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset' +FT_FILE_COLLECTION = CHANNEL_TYPE_FILE_TRANSFER + '.FUTURE.FileCollection' + +GF_CAN_ADD = 1 +GF_CAN_REMOVE = 2 +GF_CAN_RESCIND = 4 +GF_MESSAGE_ADD = 8 +GF_MESSAGE_REMOVE = 16 +GF_MESSAGE_ACCEPT = 32 +GF_MESSAGE_REJECT = 64 +GF_MESSAGE_RESCIND = 128 +GF_CHANNEL_SPECIFIC_HANDLES = 256 +GF_ONLY_ONE_GROUP = 512 +GF_HANDLE_OWNERS_NOT_AVAILABLE = 1024 +GF_PROPERTIES = 2048 +GF_MEMBERS_CHANGED_DETAILED = 4096 + +GC_REASON_NONE = 0 +GC_REASON_OFFLINE = 1 +GC_REASON_KICKED = 2 +GC_REASON_BUSY = 3 +GC_REASON_INVITED = 4 +GC_REASON_BANNED = 5 +GC_REASON_ERROR = 6 +GC_REASON_INVALID_CONTACT = 7 +GC_REASON_NO_ANSWER = 8 +GC_REASON_RENAMED = 9 +GC_REASON_PERMISSION_DENIED = 10 +GC_REASON_SEPARATED = 11 + +HS_UNHELD = 0 +HS_HELD = 1 +HS_PENDING_HOLD = 2 +HS_PENDING_UNHOLD = 3 + +HSR_NONE = 0 +HSR_REQUESTED = 1 +HSR_RESOURCE_NOT_AVAILABLE = 2 + +CALL_STATE_RINGING = 1 +CALL_STATE_QUEUED = 2 +CALL_STATE_HELD = 4 +CALL_STATE_FORWARDED = 8 + +CONN_STATUS_CONNECTED = 0 +CONN_STATUS_CONNECTING = 1 +CONN_STATUS_DISCONNECTED = 2 + +CSR_NONE_SPECIFIED = 0 +CSR_REQUESTED = 1 +CSR_NETWORK_ERROR = 2 +CSR_AUTHENTICATION_FAILED = 3 +CSR_ENCRYPTION_ERROR = 4 +CSR_NAME_IN_USE = 5 +CSR_CERT_NOT_PROVIDED = 6 +CSR_CERT_UNTRUSTED = 7 +CSR_CERT_EXPIRED = 8 +CSR_CERT_NOT_ACTIVATED = 9 +CSR_CERT_HOSTNAME_MISMATCH = 10 +CSR_CERT_FINGERPRINT_MISMATCH = 11 +CSR_CERT_SELF_SIGNED = 12 +CSR_CERT_OTHER_ERROR = 13 + +BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo' +ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties' + +CHAT_STATE_GONE = 0 +CHAT_STATE_INACTIVE = 1 +CHAT_STATE_ACTIVE = 2 +CHAT_STATE_PAUSED = 3 +CHAT_STATE_COMPOSING = 4 + +# Channel_Media_Capabilities +MEDIA_CAP_AUDIO = 1 +MEDIA_CAP_VIDEO = 2 +MEDIA_CAP_STUN = 4 +MEDIA_CAP_GTALKP2P = 8 +MEDIA_CAP_ICEUDP = 16 +MEDIA_CAP_IMMUTABLE_STREAMS = 32 + +CLIENT = 'org.freedesktop.Telepathy.Client' + +PRESENCE_OFFLINE = 1 +PRESENCE_AVAILABLE = 2 +PRESENCE_AWAY = 3 +PRESENCE_EXTENDED_AWAY = 4 +PRESENCE_HIDDEN = 5 +PRESENCE_BUSY = 6 +PRESENCE_UNKNOWN = 7 +PRESENCE_ERROR = 8 + +CONTACT_INFO_FLAG_CAN_SET = 1 +CONTACT_INFO_FLAG_PUSH = 2 +CONTACT_INFO_FIELD_FLAG_PARAMETERS_MANDATORY = 1 + +# Channel_Type_ServerAuthentication +AUTH_TYPE_SASL = 0 +AUTH_TYPE_CAPTCHA = 1 + +# Channel_Interface_SaslAuthentication +SASL_STATUS_NOT_STARTED = 0 +SASL_STATUS_IN_PROGRESS = 1 +SASL_STATUS_SERVER_SUCCEEDED = 2 +SASL_STATUS_CLIENT_ACCEPTED = 3 +SASL_STATUS_SUCCEEDED = 4 +SASL_STATUS_SERVER_FAILED = 5 +SASL_STATUS_CLIENT_FAILED = 6 + +SASL_ABORT_REASON_INVALID_CHALLENGE = 0 +SASL_ABORT_REASON_USER_ABORT = 1 + +AUTH_METHOD = CHANNEL_TYPE_SERVER_AUTHENTICATION + ".AuthenticationMethod" +AUTH_INFO = CHANNEL_TYPE_SERVER_AUTHENTICATION + ".AuthenticationInformation" +SASL_AVAILABLE_MECHANISMS = CHANNEL_IFACE_SASL_AUTH + ".AvailableMechanisms" + +# Channel_Type_ServerTLSConnection +TLS_CERT_PATH = CHANNEL_TYPE_SERVER_TLS_CONNECTION + ".ServerCertificate" +TLS_HOSTNAME = CHANNEL_TYPE_SERVER_TLS_CONNECTION + ".Hostname" + +# Connection.Interface.Location + +LOCATION_FEATURE_CAN_SET = 1 + +# Channel.Type.Text + +MT_NORMAL = 0 +MT_ACTION = 1 +MT_NOTICE = 2 +MT_AUTO_REPLY = 3 +MT_DELIVERY_REPORT = 4 + +PROTOCOL = 'org.freedesktop.Telepathy.Protocol' +PARAM_REQUIRED = 1 +PARAM_REGISTER = 2 +PARAM_HAS_DEFAULT = 4 +PARAM_SECRET = 8 +PARAM_DBUS_PROPERTY = 16 + +AUTHENTICATION = 'org.freedesktop.Telepathy.Authentication' +AUTH_TLS_CERT = AUTHENTICATION + ".TLSCertificate" + +TLS_CERT_STATE_PENDING = 0 +TLS_CERT_STATE_ACCEPTED = 1 +TLS_CERT_STATE_REJECTED = 2 + +TLS_REJECT_REASON_UNKNOWN = 0 +TLS_REJECT_REASON_UNTRUSTED = 1 diff --git a/tests/twisted/servicetest.py b/tests/twisted/servicetest.py index ecb44f6..5685eec 100644 --- a/tests/twisted/servicetest.py +++ b/tests/twisted/servicetest.py @@ -6,54 +6,26 @@ Infrastructure code for testing connection managers. from twisted.internet import glib2reactor from twisted.internet.protocol import Protocol, Factory, ClientFactory glib2reactor.install() +import sys +import time import pprint -import traceback import unittest import dbus.glib from twisted.internet import reactor +import constants as cs + tp_name_prefix = 'org.freedesktop.Telepathy' tp_path_prefix = '/org/freedesktop/Telepathy' -class TryNextHandler(Exception): - pass - -def lazy(func): - def handler(event, data): - if func(event, data): - return True - else: - raise TryNextHandler() - handler.__name__ = func.__name__ - return handler - -def match(type, **kw): - def decorate(func): - def handler(event, data, *extra, **extra_kw): - if event.type != type: - return False - - for key, value in kw.iteritems(): - if not hasattr(event, key): - return False - - if getattr(event, key) != value: - return False - - return func(event, data, *extra, **extra_kw) - - handler.__name__ = func.__name__ - return handler - - return decorate - class Event: def __init__(self, type, **kw): self.__dict__.update(kw) self.type = type + (self.subqueue, self.subtype) = type.split ("-", 1) def format_event(event): ret = ['- type %s' % event.type] @@ -68,113 +40,24 @@ def format_event(event): return ret -class EventTest: - """Somewhat odd event dispatcher for asynchronous tests. - - Callbacks are kept in a queue. Incoming events are passed to the first - callback. If the callback returns True, the callback is removed. If the - callback raises AssertionError, the test fails. If there are no more - callbacks, the test passes. The reactor is stopped when the test passes. - """ - - def __init__(self): - self.queue = [] - self.data = {'test': self} - self.timeout_delayed_call = reactor.callLater(5, self.timeout_cb) - #self.verbose = True - self.verbose = False - # ugh - self.stopping = False - - def timeout_cb(self): - print 'timed out waiting for events' - print self.queue[0] - self.fail() - - def fail(self): - # ugh; better way to stop the reactor and exit(1)? - import os - os._exit(1) - - def expect(self, f): - self.queue.append(f) - - def log(self, s): - if self.verbose: - print s - - def try_stop(self): - if self.stopping: - return True - - if not self.queue: - self.log('no handlers left; stopping') - self.stopping = True - reactor.stop() - return True - - return False - - def call_handlers(self, event): - self.log('trying %r' % self.queue[0]) - handler = self.queue.pop(0) - - try: - ret = handler(event, self.data) - if not ret: - self.queue.insert(0, handler) - except TryNextHandler, e: - if self.queue: - ret = self.call_handlers(event) - else: - ret = False - self.queue.insert(0, handler) - - return ret - - def handle_event(self, event): - if self.try_stop(): - return - - self.log('got event:') - self.log('- type: %s' % event.type) - map(self.log, format_event(event)) - - try: - ret = self.call_handlers(event) - except SystemExit, e: - if e.code: - print "Unsuccessful exit:", e - self.fail() - else: - self.queue[:] = [] - ret = True - except AssertionError, e: - print 'test failed:' - traceback.print_exc() - self.fail() - except (Exception, KeyboardInterrupt), e: - print 'error in handler:' - traceback.print_exc() - self.fail() - - if ret not in (True, False): - print ("warning: %s() returned something other than True or False" - % self.queue[0].__name__) - - if ret: - self.timeout_delayed_call.reset(5) - self.log('event handled') - else: - self.log('event not handled') - - self.log('') - self.try_stop() - class EventPattern: def __init__(self, type, **properties): self.type = type + self.predicate = None + if 'predicate' in properties: + self.predicate = properties['predicate'] + del properties['predicate'] self.properties = properties + (self.subqueue, self.subtype) = type.split ("-", 1) + + def __repr__(self): + properties = dict(self.properties) + + if self.predicate is not None: + properties['predicate'] = self.predicate + + return '%s(%r, **%r)' % ( + self.__class__.__name__, self.type, properties) def match(self, event): if event.type != self.type: @@ -187,7 +70,11 @@ class EventPattern: except AttributeError: return False - return True + if self.predicate is None or self.predicate(event): + return True + + return False + class TimeoutError(Exception): pass @@ -200,6 +87,8 @@ class BaseEventQueue: def __init__(self, timeout=None): self.verbose = False + self.forbidden_events = set() + self.event_queues = {} if timeout is None: self.timeout = 5 @@ -210,16 +99,56 @@ class BaseEventQueue: if self.verbose: print s + def log_queues(self, queues): + self.log ("Waiting for event on: %s" % ", ".join(queues)) + + def log_event(self, event): + self.log('got event:') + + if self.verbose: + map(self.log, format_event(event)) + + def forbid_events(self, patterns): + """ + Add patterns (an iterable of EventPattern) to the set of forbidden + events. If a forbidden event occurs during an expect or expect_many, + the test will fail. + """ + self.forbidden_events.update(set(patterns)) + + def unforbid_events(self, patterns): + """ + Remove 'patterns' (an iterable of EventPattern) from the set of + forbidden events. These must be the same EventPattern pointers that + were passed to forbid_events. + """ + self.forbidden_events.difference_update(set(patterns)) + + def _check_forbidden(self, event): + for e in self.forbidden_events: + if e.match(event): + print "forbidden event occurred:" + for x in format_event(event): + print x + assert False + def expect(self, type, **kw): + """ + Waits for an event matching the supplied pattern to occur, and returns + it. For example, to await a D-Bus signal with particular arguments: + + e = q.expect('dbus-signal', signal='Badgers', args=["foo", 42]) + """ pattern = EventPattern(type, **kw) + t = time.time() while True: - event = self.wait() - self.log('got event:') - map(self.log, format_event(event)) + event = self.wait([pattern.subqueue]) + self._check_forbidden(event) if pattern.match(event): - self.log('handled') + self.log('handled, took %0.3f ms' + % ((time.time() - t) * 1000.0) ) self.log('') return event @@ -227,16 +156,57 @@ class BaseEventQueue: self.log('') def expect_many(self, *patterns): + """ + Waits for events matching all of the supplied EventPattern instances to + return, and returns a list of events in the same order as the patterns + they matched. After a pattern is successfully matched, it is not + considered for future events; if more than one unsatisfied pattern + matches an event, the first "wins". + + Note that the expected events may occur in any order. If you're + expecting a series of events in a particular order, use repeated calls + to expect() instead. + + This method is useful when you're awaiting a number of events which may + happen in any order. For instance, in telepathy-gabble, calling a D-Bus + method often causes a value to be returned immediately, as well as a + query to be sent to the server. Since these events may reach the test + in either order, the following is incorrect and will fail if the IQ + happens to reach the test first: + + ret = q.expect('dbus-return', method='Foo') + query = q.expect('stream-iq', query_ns=ns.FOO) + + The following would be correct: + + ret, query = q.expect_many( + EventPattern('dbus-return', method='Foo'), + EventPattern('stream-iq', query_ns=ns.FOO), + ) + """ ret = [None] * len(patterns) + t = time.time() while None in ret: - event = self.wait() - self.log('got event:') - map(self.log, format_event(event)) + try: + queues = set() + for i, pattern in enumerate(patterns): + if ret[i] is None: + queues.add(pattern.subqueue) + event = self.wait(queues) + except TimeoutError: + self.log('timeout') + self.log('still expecting:') + for i, pattern in enumerate(patterns): + if ret[i] is None: + self.log(' - %r' % pattern) + raise + self._check_forbidden(event) for i, pattern in enumerate(patterns): - if pattern.match(event): - self.log('handled') + if ret[i] is None and pattern.match(event): + self.log('handled, took %0.3f ms' + % ((time.time() - t) * 1000.0) ) self.log('') ret[i] = event break @@ -249,9 +219,7 @@ class BaseEventQueue: def demand(self, type, **kw): pattern = EventPattern(type, **kw) - event = self.wait() - self.log('got event:') - map(self.log, format_event(event)) + event = self.wait([pattern.subqueue]) if pattern.match(event): self.log('handled') @@ -261,14 +229,34 @@ class BaseEventQueue: self.log('not handled') raise RuntimeError('expected %r, got %r' % (pattern, event)) + def queues_available(self, queues): + if queues == None: + return self.event_queues.keys() + else: + available = self.event_queues.keys() + return filter(lambda x: x in available, queues) + + + def pop_next(self, queue): + events = self.event_queues[queue] + e = events.pop(0) + if not events: + self.event_queues.pop (queue) + return e + + def append(self, event): + self.log ("Adding to queue") + self.log_event (event) + self.event_queues[event.subqueue] = \ + self.event_queues.get(event.subqueue, []) + [event] + class IteratingEventQueue(BaseEventQueue): """Event queue that works by iterating the Twisted reactor.""" def __init__(self, timeout=None): BaseEventQueue.__init__(self, timeout) - self.events = [] - def wait(self): + def wait(self, queues=None): stop = [False] def later(): @@ -276,58 +264,92 @@ class IteratingEventQueue(BaseEventQueue): delayed_call = reactor.callLater(self.timeout, later) - while (not self.events) and (not stop[0]): - reactor.iterate(0.1) + self.log_queues(queues) + + qa = self.queues_available(queues) + while not qa and (not stop[0]): + reactor.iterate(0.01) + qa = self.queues_available(queues) - if self.events: + if qa: delayed_call.cancel() - return self.events.pop(0) + e = self.pop_next (qa[0]) + self.log_event (e) + return e else: raise TimeoutError - def append(self, event): - self.events.append(event) - - # compatibility - handle_event = append - class TestEventQueue(BaseEventQueue): def __init__(self, events): BaseEventQueue.__init__(self) - self.events = events + for e in events: + self.append (e) - def wait(self): - if self.events: - return self.events.pop(0) + def wait(self, queues = None): + qa = self.queues_available(queues) + + if qa: + return self.pop_next (qa[0]) else: raise TimeoutError class EventQueueTest(unittest.TestCase): def test_expect(self): - queue = TestEventQueue([Event('foo'), Event('bar')]) - assert queue.expect('foo').type == 'foo' - assert queue.expect('bar').type == 'bar' + queue = TestEventQueue([Event('test-foo'), Event('test-bar')]) + assert queue.expect('test-foo').type == 'test-foo' + assert queue.expect('test-bar').type == 'test-bar' def test_expect_many(self): - queue = TestEventQueue([Event('foo'), Event('bar')]) + queue = TestEventQueue([Event('test-foo'), + Event('test-bar')]) bar, foo = queue.expect_many( - EventPattern('bar'), - EventPattern('foo')) - assert bar.type == 'bar' - assert foo.type == 'foo' + EventPattern('test-bar'), + EventPattern('test-foo')) + assert bar.type == 'test-bar' + assert foo.type == 'test-foo' + + def test_expect_many2(self): + # Test that events are only matched against patterns that haven't yet + # been matched. This tests a regression. + queue = TestEventQueue([Event('test-foo', x=1), Event('test-foo', x=2)]) + foo1, foo2 = queue.expect_many( + EventPattern('test-foo'), + EventPattern('test-foo')) + assert foo1.type == 'test-foo' and foo1.x == 1 + assert foo2.type == 'test-foo' and foo2.x == 2 + + def test_expect_queueing(self): + queue = TestEventQueue([Event('foo-test', x=1), + Event('foo-test', x=2)]) + + queue.append(Event('bar-test', x=1)) + queue.append(Event('bar-test', x=2)) + + queue.append(Event('baz-test', x=1)) + queue.append(Event('baz-test', x=2)) + + for x in xrange(1,2): + e = queue.expect ('baz-test') + assertEquals (x, e.x) + + e = queue.expect ('bar-test') + assertEquals (x, e.x) + + e = queue.expect ('foo-test') + assertEquals (x, e.x) def test_timeout(self): queue = TestEventQueue([]) - self.assertRaises(TimeoutError, queue.expect, 'foo') + self.assertRaises(TimeoutError, queue.expect, 'test-foo') def test_demand(self): - queue = TestEventQueue([Event('foo'), Event('bar')]) - foo = queue.demand('foo') - assert foo.type == 'foo' + queue = TestEventQueue([Event('test-foo'), Event('test-bar')]) + foo = queue.demand('test-foo') + assert foo.type == 'test-foo' def test_demand_fail(self): - queue = TestEventQueue([Event('foo'), Event('bar')]) - self.assertRaises(RuntimeError, queue.demand, 'bar') + queue = TestEventQueue([Event('test-foo'), Event('test-bar')]) + self.assertRaises(RuntimeError, queue.demand, 'test-bar') def unwrap(x): """Hack to unwrap D-Bus values, so that they're easier to read when @@ -342,7 +364,10 @@ def unwrap(x): if isinstance(x, dict): return dict([(unwrap(k), unwrap(v)) for k, v in x.iteritems()]) - for t in [unicode, str, long, int, float, bool]: + if isinstance(x, dbus.Boolean): + return bool(x) + + for t in [unicode, str, long, int, float]: if isinstance(x, t): return t(x) @@ -353,21 +378,33 @@ def call_async(test, proxy, method, *args, **kw): resulting method return/error.""" def reply_func(*ret): - test.handle_event(Event('dbus-return', method=method, + test.append(Event('dbus-return', method=method, value=unwrap(ret))) def error_func(err): - test.handle_event(Event('dbus-error', method=method, error=err)) + test.append(Event('dbus-error', method=method, error=err, + name=err.get_dbus_name(), message=str(err))) method_proxy = getattr(proxy, method) kw.update({'reply_handler': reply_func, 'error_handler': error_func}) method_proxy(*args, **kw) +def sync_dbus(bus, q, conn): + # Dummy D-Bus method call + # This won't do the right thing unless the proxy has a unique name. + assert conn.object.bus_name.startswith(':') + root_object = bus.get_object(conn.object.bus_name, '/') + call_async( + q, dbus.Interface(root_object, 'org.freedesktop.DBus.Peer'), 'Ping') + q.expect('dbus-return', method='Ping') class ProxyWrapper: def __init__(self, object, default, others): self.object = object self.default_interface = dbus.Interface(object, default) + self.Properties = dbus.Interface(object, dbus.PROPERTIES_IFACE) + self.TpProperties = \ + dbus.Interface(object, tp_name_prefix + '.Properties') self.interfaces = dict([ (name, dbus.Interface(object, iface)) for name, iface in others.iteritems()]) @@ -381,8 +418,36 @@ class ProxyWrapper: return getattr(self.default_interface, name) -def prepare_test(event_func, name, proto, params): - bus = dbus.SessionBus() +def wrap_connection(conn): + return ProxyWrapper(conn, tp_name_prefix + '.Connection', + dict([ + (name, tp_name_prefix + '.Connection.Interface.' + name) + for name in ['Aliasing', 'Avatars', 'Capabilities', 'Contacts', + 'Presence', 'SimplePresence', 'Requests']] + + [('Peer', 'org.freedesktop.DBus.Peer'), + ('ContactCapabilities', cs.CONN_IFACE_CONTACT_CAPS), + ('ContactInfo', cs.CONN_IFACE_CONTACT_INFO), + ('Location', cs.CONN_IFACE_LOCATION), + ('Future', tp_name_prefix + '.Connection.FUTURE'), + ('MailNotification', cs.CONN_IFACE_MAIL_NOTIFICATION), + ('ContactList', cs.CONN_IFACE_CONTACT_LIST), + ('ContactGroups', cs.CONN_IFACE_CONTACT_GROUPS), + ])) + +def wrap_channel(chan, type_, extra=None): + interfaces = { + type_: tp_name_prefix + '.Channel.Type.' + type_, + 'Group': tp_name_prefix + '.Channel.Interface.Group', + } + + if extra: + interfaces.update(dict([ + (name, tp_name_prefix + '.Channel.Interface.' + name) + for name in extra])) + + return ProxyWrapper(chan, tp_name_prefix + '.Channel', interfaces) + +def make_connection(bus, event_func, name, proto, params): cm = bus.get_object( tp_name_prefix + '.ConnectionManager.%s' % name, tp_path_prefix + '/ConnectionManager/%s' % name) @@ -390,28 +455,25 @@ def prepare_test(event_func, name, proto, params): connection_name, connection_path = cm_iface.RequestConnection( proto, params) - conn = bus.get_object(connection_name, connection_path) - conn = ProxyWrapper(conn, tp_name_prefix + '.Connection', - dict([ - (name, tp_name_prefix + '.Connection.Interface.' + name) - for name in ['Aliasing', 'Avatars', 'Capabilities', 'Presence']] + - [('Peer', 'org.freedesktop.DBus.Peer')])) + conn = wrap_connection(bus.get_object(connection_name, connection_path)) bus.add_signal_receiver( lambda *args, **kw: event_func( Event('dbus-signal', - path=unwrap(kw['path'])[len(tp_path_prefix):], - signal=kw['member'], args=map(unwrap, args))), + path=unwrap(kw['path']), + signal=kw['member'], args=map(unwrap, args), + interface=kw['interface'])), None, # signal name None, # interface cm._named_service, path_keyword='path', member_keyword='member', + interface_keyword='interface', byte_arrays=True ) - return bus, conn + return conn def make_channel_proxy(conn, path, iface): bus = dbus.SessionBus() @@ -419,36 +481,39 @@ def make_channel_proxy(conn, path, iface): chan = dbus.Interface(chan, tp_name_prefix + '.' + iface) return chan -def load_event_handlers(): - path, _, _, _ = traceback.extract_stack()[0] - import compiler - import __main__ - ast = compiler.parseFile(path) - return [ - getattr(__main__, node.name) - for node in ast.node.asList() - if node.__class__ == compiler.ast.Function and - node.name.startswith('expect_')] - +# block_reading can be used if the test want to choose when we start to read +# data from the socket. class EventProtocol(Protocol): - def __init__(self, queue=None): + def __init__(self, queue=None, block_reading=False): self.queue = queue def dataReceived(self, data): if self.queue is not None: - self.queue.handle_event(Event('socket-data', protocol=self, + self.queue.append(Event('socket-data', protocol=self, data=data)) def sendData(self, data): self.transport.write(data) + def connectionMade(self): + if self.block_reading: + self.transport.stopReading() + + def connectionLost(self, reason=None): + if self.queue is not None: + self.queue.append(Event('socket-disconnected', protocol=self)) + class EventProtocolFactory(Factory): - def __init__(self, queue): + def __init__(self, queue, block_reading=False): self.queue = queue + self.block_reading = block_reading + + def _create_protocol(self): + return EventProtocol(self.queue, self.block_reading) def buildProtocol(self, addr): - proto = EventProtocol(self.queue) - self.queue.handle_event(Event('socket-connected', protocol=proto)) + proto = self._create_protocol() + self.queue.append(Event('socket-connected', protocol=proto)) return proto class EventProtocolClientFactory(EventProtocolFactory, ClientFactory): @@ -456,7 +521,7 @@ class EventProtocolClientFactory(EventProtocolFactory, ClientFactory): def watch_tube_signals(q, tube): def got_signal_cb(*args, **kwargs): - q.handle_event(Event('tube-signal', + q.append(Event('tube-signal', path=kwargs['path'], signal=kwargs['member'], args=map(unwrap, args), @@ -466,6 +531,81 @@ def watch_tube_signals(q, tube): path_keyword='path', member_keyword='member', byte_arrays=True) +def pretty(x): + return pprint.pformat(unwrap(x)) + +def assertEquals(expected, value): + if expected != value: + raise AssertionError( + "expected:\n%s\ngot:\n%s" % (pretty(expected), pretty(value))) + +def assertSameSets(expected, value): + exp_set = set(expected) + val_set = set(value) + + if exp_set != val_set: + raise AssertionError( + "expected contents:\n%s\ngot:\n%s" % ( + pretty(exp_set), pretty(val_set))) + +def assertNotEquals(expected, value): + if expected == value: + raise AssertionError( + "expected something other than:\n%s" % pretty(value)) + +def assertContains(element, value): + if element not in value: + raise AssertionError( + "expected:\n%s\nin:\n%s" % (pretty(element), pretty(value))) + +def assertDoesNotContain(element, value): + if element in value: + raise AssertionError( + "expected:\n%s\nnot in:\n%s" % (pretty(element), pretty(value))) + +def assertLength(length, value): + if len(value) != length: + raise AssertionError("expected: length %d, got length %d:\n%s" % ( + length, len(value), pretty(value))) + +def assertFlagsSet(flags, value): + masked = value & flags + if masked != flags: + raise AssertionError( + "expected flags %u, of which only %u are set in %u" % ( + flags, masked, value)) + +def assertFlagsUnset(flags, value): + masked = value & flags + if masked != 0: + raise AssertionError( + "expected none of flags %u, but %u are set in %u" % ( + flags, masked, value)) + +def install_colourer(): + def red(s): + return '\x1b[31m%s\x1b[0m' % s + + def green(s): + return '\x1b[32m%s\x1b[0m' % s + + patterns = { + 'handled': green, + 'not handled': red, + } + + class Colourer: + def __init__(self, fh, patterns): + self.fh = fh + self.patterns = patterns + + def write(self, s): + f = self.patterns.get(s, lambda x: x) + self.fh.write(f(s)) + + sys.stdout = Colourer(sys.stdout, patterns) + return sys.stdout + if __name__ == '__main__': unittest.main() diff --git a/tests/twisted/sofiatest.py b/tests/twisted/sofiatest.py index 149cacb..94263b9 100644 --- a/tests/twisted/sofiatest.py +++ b/tests/twisted/sofiatest.py @@ -20,6 +20,9 @@ class SipProxy(sip.RegisterProxy): def register(self, message, host, port): if hasattr(self, 'registrar_handler'): + self.event_func(servicetest.Event('sip-register', + uri=str(message.uri), headers=message.headers, body=message.body, + sip_message=message, host=host, port=port)) if self.registrar_handler(message, host, port): sip.RegisterProxy.register(self, message, host, port) else: @@ -48,9 +51,14 @@ def prepare_test(event_func, register_cb, params=None): } if params is not None: - actual_params.update(params) + for k, v in params.items(): + if v is None: + actual_params.pop(k, None) + else: + actual_params[k] = v - bus, conn = servicetest.prepare_test(event_func, + bus = dbus.SessionBus() + conn = servicetest.make_connection(bus, event_func, 'sofiasip', 'sip', actual_params) port = int(actual_params['port']) diff --git a/tests/twisted/test-handle-normalisation.py b/tests/twisted/test-handle-normalisation.py index cf741eb..710ed72 100644 --- a/tests/twisted/test-handle-normalisation.py +++ b/tests/twisted/test-handle-normalisation.py @@ -1,14 +1,16 @@ -from servicetest import unwrap, match -from sofiatest import go +from servicetest import assertEquals +from sofiatest import exec_test import dbus +import constants as cs -@match('dbus-signal', signal='StatusChanged', args=[1, 1]) -def expect_connecting(event, data): - return True +def test(q, bus, conn, sip): + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', + args=[cs.CONN_STATUS_CONNECTING, cs.CSR_REQUESTED]) + q.expect('dbus-signal', signal='StatusChanged', + args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) -@match('dbus-signal', signal='StatusChanged', args=[0, 1]) -def expect_connected(event, data): tests = [ ('sip:test@localhost', 'sip:test@localhost'), ('test@localhost', 'sip:test@localhost'), ('test', 'sip:test@127.0.0.1'), @@ -29,22 +31,15 @@ def expect_connected(event, data): orig = [ x[0] for x in tests ] expect = [ x[1] for x in tests ] - handles = data['conn_iface'].RequestHandles(1, orig) - names = data['conn_iface'].InspectHandles(1, handles) + handles = conn.RequestHandles(1, orig) + names = conn.InspectHandles(1, handles) - for a,b in zip(expect, map(lambda x: str(unwrap(x)), names)): - if a != b: - print a, b - raise Exception("test failed") + for a,b in zip(expect, names): + assertEquals(a, b) - data['conn_iface'].Disconnect() - return True - -@match('dbus-signal', signal='StatusChanged', args=[2, 1]) -def expect_disconnected(event, data): - return True + conn.Disconnect() + q.expect('dbus-signal', signal='StatusChanged', + args=[cs.CONN_STATUS_DISCONNECTED, cs.CSR_REQUESTED]) if __name__ == '__main__': - go() - - + exec_test(test) diff --git a/tests/twisted/test-message.py b/tests/twisted/test-message.py index 913519f..055cccf 100644 --- a/tests/twisted/test-message.py +++ b/tests/twisted/test-message.py @@ -141,8 +141,7 @@ def test(q, bus, conn, sip): send_message(sip, ua_via, 'How are you doing now, old pal?', sender=contact) - event = q.expect('dbus-signal', signal='Received') - assert tp_path_prefix + event.path == chan, (event.path, chan) + event = q.expect('dbus-signal', signal='Received', path=chan) assert event.args[5] == 'How are you doing now, old pal?' pending_msgs.append(tuple(event.args)) @@ -156,8 +155,7 @@ def test(q, bus, conn, sip): dbus.Interface(requested_obj, CHANNEL).Close() del requested_obj - event = q.expect('dbus-signal', signal='Closed') - assert tp_path_prefix + event.path == chan, (event.path, chan) + event = q.expect('dbus-signal', signal='Closed', path=chan) requested_obj, handle = test_new_channel (q, bus, conn, target_uri=contact, diff --git a/tests/twisted/test-register-sasl.py b/tests/twisted/test-register-sasl.py new file mode 100644 index 0000000..f4ef924 --- /dev/null +++ b/tests/twisted/test-register-sasl.py @@ -0,0 +1,36 @@ +""" +Test SIP registration failure. +""" + +import dbus + +from sofiatest import exec_test + +def test(q, bus, conn, sip): + conn.Connect() + q.expect('dbus-signal', signal='StatusChanged', args=[1, 1]) + + q.expect('sip-register') + + nc = q.expect('dbus-signal', signal='NewChannels') + (((path, props),),) = nc.args + assert props['org.freedesktop.Telepathy.Channel.ChannelType'] == \ + 'org.freedesktop.Telepathy.Channel.Type.ServerAuthentication' + assert props['org.freedesktop.Telepathy.Channel.Interface.SASLAuthentication.AvailableMechanisms'] == \ + ['X-TELEPATHY-PASSWORD'] + + chan = dbus.Interface(bus.get_object(conn._named_service, path), + "org.freedesktop.Telepathy.Channel.Interface.SASLAuthentication") + + chan.StartMechanismWithData('X-TELEPATHY-PASSWORD', 'wrong password') + chan.AcceptSASL() + + q.expect('sip-register') + + q.expect('dbus-signal', signal='StatusChanged', args=[2, 3]) + return True + +if __name__ == '__main__': + exec_test(test, register_cb=lambda *args: False, + params={"password": None}) + diff --git a/tests/twisted/test-register.py b/tests/twisted/test-register.py index b44b93d..9a292e7 100644 --- a/tests/twisted/test-register.py +++ b/tests/twisted/test-register.py @@ -13,5 +13,5 @@ def test(q, bus, conn, sip): return True if __name__ == '__main__': - exec_test(test) + exec_test(test, params={"password": None}) diff --git a/tools/Makefile.am b/tools/Makefile.am index b5b2331..f9d9e29 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -17,7 +17,8 @@ EXTRA_DIST = \ glib-signals-marshal-gen.py \ identity.xsl \ lcov.am \ - libglibcodegen.py + libglibcodegen.py \ + telepathy.am CLEANFILES = libglibcodegen.pyc libglibcodegen.pyo $(noinst_SCRIPTS) diff --git a/tools/make-release-mail.py b/tools/make-release-mail.py new file mode 100644 index 0000000..2bd7c2b --- /dev/null +++ b/tools/make-release-mail.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# vim: set fileencoding=utf-8 : +# +# Hello. This is make-release-mail.py from the Telepathy project. It's +# designed to turn an item from a NEWS file into a mail suitable for sending +# to <telepathy@lists.freedesktop.org>. I hope that you enjoy your stay. + +import sys + +def extract_description(package, version, news_path): + release_name = [] + details = [] + + with open(news_path) as f: + lines = (line for line in f.readlines()) + for line in lines: + # Find the 'telepathy-foo 0.1.2' header + if line.startswith("%s %s" % (package, version)): + break + + # Skip the ====== line, and the first blank line + lines.next() + lines.next() + + got_release_name = False + + for line in lines: + line = line.rstrip() + # If we hit the next version header, we're done + if line.startswith(package): + break + # Else, if we hit a blank line and we're still reading the release + # name, we're done with the release name. + elif not got_release_name and line == '': + got_release_name = True + # Otherwise, append this to the relevant list + elif not got_release_name: + release_name.append(line) + else: + details.append(line) + + assert got_release_name, (release_name, details) + + # We rstrip details because it picks up a trailing blank line + return ('\n'.join(release_name), '\n'.join(details).rstrip()) + +BASE_URL = 'http://telepathy.freedesktop.org/releases' + +def main(package, version, news_path): + release_name, details = extract_description(package, version, news_path) + + print """ +%(release_name)s + +tarball: %(base_url)s/%(package)s/%(package)s-%(version)s.tar.gz +signature: %(base_url)s/%(package)s/%(package)s-%(version)s.tar.gz.asc + +%(details)s""".strip().rstrip() % { + 'base_url': BASE_URL, + 'package': package, + 'version': version, + 'release_name': release_name, + 'details': details, + } + +if __name__ == '__main__': + try: + package, version, news_path = sys.argv[1:] + + main(package, version, news_path) + except ValueError, e: + sys.stderr.write( + 'Usage: %s package-name package.version.number path/to/NEWS\n' % + sys.argv[0]) + sys.stderr.flush() + sys.exit(1) diff --git a/tools/make-version-script.py b/tools/make-version-script.py new file mode 100644 index 0000000..0d30aa3 --- /dev/null +++ b/tools/make-version-script.py @@ -0,0 +1,208 @@ +#!/usr/bin/python + +"""Construct a GNU ld or Debian dpkg version-script from a set of +RFC822-style symbol lists. + +Usage: + make-version-script.py [--symbols SYMBOLS] [--unreleased-version VER] + [--dpkg "LIBRARY.so.0 LIBRARY0 #MINVER#"] + [--dpkg-build-depends-package LIBRARY-dev] + [FILES...] + +Each FILE starts with RFC822-style headers "Version:" (the name of the +symbol version, e.g. FOO_1.2.3) and "Extends:" (either the previous +version, or "-" if this is the first version). Next there is a blank +line, then a list of C symbols one per line. + +Comments (lines starting with whitespace + "#") are allowed and ignored. + +If --symbols is given, SYMBOLS lists the symbols actually exported by +the library (one per line). If --unreleased-version is given, any symbols +in SYMBOLS but not in FILES are assigned to that version; otherwise, any +such symbols cause an error. + +If --dpkg is given, produce a Debian dpkg-gensymbols file instead of a +GNU ld version-script. The argument to --dpkg is the first line of the +resulting symbols file, and --dpkg-build-depends-package can optionally +be used to set the Build-Depends-Package field. + +This script originates in telepathy-glib <http://telepathy.freedesktop.org/> - +please send us any changes that are needed. +""" + +# Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> +# Copyright (C) 2008 Nokia Corporation +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. + +import sys +from getopt import gnu_getopt + + +def e(format, *args): + sys.stderr.write((format + '\n') % args) + + +def main(abifiles, symbols=None, unreleased_version=None, + dpkg=False, dpkg_first_line=None, dpkg_build_depends_package=None): + + gnuld = not dpkg + symbol_set = None + + if symbols is not None: + symbol_set = open(symbols, 'r').readlines() + symbol_set = map(str.strip, symbol_set) + symbol_set = set(symbol_set) + + versioned_symbols = set() + + dpkg_symbols = [] + dpkg_versions = [] + + if dpkg: + assert dpkg_first_line is not None + print dpkg_first_line + if dpkg_build_depends_package is not None: + print "* Build-Depends-Package: %s" % dpkg_build_depends_package + + for filename in abifiles: + lines = open(filename, 'r').readlines() + + version = None + extends = None + release = None + + for i, line in enumerate(lines): + line = line.strip() + + if line.startswith('#'): + continue + elif not line: + # the transition betwen headers and symbols + cut = i + 1 + break + elif line.lower().startswith('version:'): + line = line[8:].strip() + version = line + continue + elif line.lower().startswith('extends:'): + line = line[8:].strip() + extends = line + continue + elif line.lower().startswith('release:'): + release = line[8:].strip() + continue + else: + e('Could not understand line in %s header: %s', filename, line) + raise SystemExit(1) + + else: + e('No symbols in %s', filename) + raise SystemExit(1) + + if version is None: + e('No Versions: header in %s', filename) + raise SystemExit(1) + + if extends is None: + e('No Extends: header in %s', filename) + raise SystemExit(1) + + if release is None and dpkg: + e('No Release: header in %s', filename) + raise SystemExit(1) + + if dpkg: + dpkg_versions.append('%s@%s %s' % (version, version, release)) + + lines = lines[cut:] + + if gnuld: + print "%s {" % version + print " global:" + + for symbol in lines: + symbol = symbol.strip() + + if symbol.startswith('#'): + continue + + if gnuld: + print " %s;" % symbol + elif dpkg: + dpkg_symbols.append('%s@%s %s' % (symbol, version, release)) + + if symbol in versioned_symbols: + raise AssertionError('Symbol %s is in version %s and an ' + 'earlier version' % (symbol, version)) + + versioned_symbols.add(symbol) + + if gnuld: + if extends == '-': + print " local:" + print " *;" + print "};" + else: + print "} %s;" % extends + print + + if dpkg: + dpkg_symbols.sort() + dpkg_versions.sort() + + for x in dpkg_versions: + print " %s" % x + + for x in dpkg_symbols: + print " %s" % x + + if symbol_set is not None: + missing = versioned_symbols - symbol_set + + if missing: + e('These symbols have disappeared:') + + for symbol in missing: + e(' %s', symbol) + + raise SystemExit(1) + + unreleased = symbol_set - versioned_symbols + + if unreleased: + if unreleased_version is None: + e('Unversioned symbols are not allowed in releases:') + + for symbol in unreleased: + e(' %s', symbol) + + raise SystemExit(1) + + if gnuld: + print "%s {" % unreleased_version + print " global:" + + for symbol in unreleased: + print " %s;" % symbol + + print "} %s;" % version + + +if __name__ == '__main__': + options, argv = gnu_getopt (sys.argv[1:], '', + ['symbols=', 'unreleased-version=', + 'dpkg=', 'dpkg-build-depends-package=']) + + opts = {'dpkg': False} + + for option, value in options: + if option == '--dpkg': + opts['dpkg'] = True + opts['dpkg_first_line'] = value + else: + opts[option.lstrip('-').replace('-', '_')] = value + + main(argv, **opts) diff --git a/tools/telepathy.am b/tools/telepathy.am new file mode 100644 index 0000000..45baa77 --- /dev/null +++ b/tools/telepathy.am @@ -0,0 +1,65 @@ +## Useful top-level Makefile.am snippets for Telepathy projects. + +dist-hook: + chmod u+w ${distdir}/ChangeLog + if test -d ${top_srcdir}/.git; then \ + git log --date=iso $(CHANGELOG_RANGE) > ${distdir}/ChangeLog; \ + fi + +distcheck-hook: + @test "z$(CHECK_FOR_UNRELEASED)" = z || \ + case @VERSION@ in \ + *.*.*.*|*+) ;; \ + *) \ + if grep -r UNRELEASED $(CHECK_FOR_UNRELEASED); \ + then \ + echo "^^^ This is meant to be a release, but some files say UNRELEASED" >&2; \ + exit 2; \ + fi \ + ;; \ + esac + +_is-release-check: + @case @VERSION@ in \ + (*.*.*.*|*+) \ + echo "Hey! @VERSION@ is not a release!" >&2; \ + exit 2; \ + ;; \ + esac + @if ! git diff --no-ext-diff --quiet --exit-code; then \ + echo "Hey! Your tree is dirty! No release for you." >&2; \ + exit 2; \ + fi + +%.tar.gz.asc: %.tar.gz + $(AM_V_GEN)gpg --detach-sign --armor $@ + +@PACKAGE@-@VERSION@.tar.gz: _is-release-check check distcheck + +maintainer-prepare-release: _is-release-check check distcheck release-mail + git tag -s @PACKAGE@-@VERSION@ -m @PACKAGE@' '@VERSION@ + gpg --detach-sign --armor @PACKAGE@-@VERSION@.tar.gz + +release-mail: NEWS + $(AM_V_GEN)(python $(top_srcdir)/tools/make-release-mail.py \ + @PACKAGE@ @VERSION@ $(top_srcdir)/NEWS > $@.tmp && \ + mv $@.tmp $@) + +maintainer-upload-release: _maintainer-upload-release + +_maintainer-upload-release-check: _is-release-check + test -f @PACKAGE@-@VERSION@.tar.gz + test -f @PACKAGE@-@VERSION@.tar.gz.asc + gpg --verify @PACKAGE@-@VERSION@.tar.gz.asc + +_maintainer-upload-release: _maintainer-upload-release-check + rsync -vzP @PACKAGE@-@VERSION@.tar.gz telepathy.freedesktop.org:/srv/telepathy.freedesktop.org/www/releases/@PACKAGE@/@PACKAGE@-@VERSION@.tar.gz + rsync -vzP @PACKAGE@-@VERSION@.tar.gz.asc telepathy.freedesktop.org:/srv/telepathy.freedesktop.org/www/releases/@PACKAGE@/@PACKAGE@-@VERSION@.tar.gz.asc + +maintainer-make-release: maintainer-prepare-release maintainer-upload-release + @echo "Now:" + @echo " • bump the nano-version;" + @echo " • push the branch and tags upstream; and" + @echo " • send release-mail to <telepathy@lists.freedesktop.org>." + +## vim:set ft=automake: |