diff options
author | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2010-11-23 16:12:53 +0000 |
---|---|---|
committer | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2010-11-23 16:12:58 +0000 |
commit | 684f6bd86d6fbd72b759af1587c7c37777e32791 (patch) | |
tree | 1060126a9eadfcfae8730b0979598913667c6c95 | |
parent | d56b1f06f7576b17208943374105f7e87b49ee61 (diff) | |
parent | 1c58961211f73bf3dd39f3bac4127a0cd7a11bb4 (diff) |
Merge branch 'protocol'
Reviewed-by: Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
Bug: https://bugs.freedesktop.org/show_bug.cgi?id=31720
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/media-factory.c | 11 | ||||
-rw-r--r-- | src/protocol.c | 515 | ||||
-rw-r--r-- | src/protocol.h | 75 | ||||
-rw-r--r-- | src/sip-connection-helpers.c | 28 | ||||
-rw-r--r-- | src/sip-connection-helpers.h | 5 | ||||
-rw-r--r-- | src/sip-connection-manager.c | 428 | ||||
-rw-r--r-- | src/sip-connection.c | 19 | ||||
-rw-r--r-- | src/sip-connection.h | 2 | ||||
-rw-r--r-- | src/text-factory.c | 9 | ||||
-rw-r--r-- | src/write-mgr-file.c | 374 | ||||
-rw-r--r-- | tests/twisted/Makefile.am | 1 | ||||
-rw-r--r-- | tests/twisted/cm/protocol.py | 58 | ||||
-rw-r--r-- | tests/twisted/constants.py | 400 | ||||
-rw-r--r-- | tests/twisted/servicetest.py | 560 | ||||
-rw-r--r-- | tests/twisted/sofiatest.py | 3 | ||||
-rw-r--r-- | tests/twisted/test-handle-normalisation.py | 39 | ||||
-rw-r--r-- | tests/twisted/test-message.py | 6 |
18 files changed, 1800 insertions, 735 deletions
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..1c8bb57 --- /dev/null +++ b/src/protocol.c @@ -0,0 +1,515 @@ +/* + * 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); + break; + } + + 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 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; + + 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.c b/src/sip-connection.c index bc175cf..d383dfb 100644 --- a/src/sip-connection.c +++ b/src/sip-connection.c @@ -422,15 +422,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; 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/text-factory.c b/src/text-factory.c index 881891e..a9cb38c 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..bc49772 100644 --- a/tests/twisted/Makefile.am +++ b/tests/twisted/Makefile.am @@ -1,4 +1,5 @@ TWISTED_TESTS = \ + cm/protocol.py \ test-register.py \ test-register-fail.py \ test-handle-normalisation.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..2ec5b75 100644 --- a/tests/twisted/sofiatest.py +++ b/tests/twisted/sofiatest.py @@ -50,7 +50,8 @@ def prepare_test(event_func, register_cb, params=None): if params is not None: actual_params.update(params) - 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 0f0b61c..95ad159 100644 --- a/tests/twisted/test-message.py +++ b/tests/twisted/test-message.py @@ -135,8 +135,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)) @@ -150,8 +149,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, |