/* * gabble-call-stream.c - Source for GabbleCallStream * Copyright (C) 2009 Collabora Ltd. * @author Sjoerd Simons * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include #include #include #include #include "call-stream.h" #include "connection.h" #include "jingle-tp-util.h" #include "util.h" #define DEBUG_FLAG GABBLE_DEBUG_MEDIA #include "debug.h" static GPtrArray *gabble_call_stream_add_candidates ( TpBaseMediaCallStream *stream, const GPtrArray *candidates, GError **error); static gboolean gabble_call_stream_set_sending (TpBaseMediaCallStream *stream, gboolean sending, GError **error); static void gabble_call_stream_request_receiving (TpBaseMediaCallStream *stream, TpHandle contact, gboolean receive); G_DEFINE_TYPE(GabbleCallStream, gabble_call_stream, TP_TYPE_BASE_MEDIA_CALL_STREAM) /* properties */ enum { PROP_JINGLE_CONTENT = 1, /* Media interface properties */ PROP_LOCAL_CANDIDATES, PROP_ENDPOINTS, PROP_TRANSPORT, PROP_STUN_SERVERS, PROP_RELAY_INFO, PROP_HAS_SERVER_INFO, PROP_CAN_REQUEST_RECEIVING }; #if 0 /* signal enum */ enum { LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = {0}; #endif /* private structure */ struct _GabbleCallStreamPrivate { gboolean dispose_has_run; WockyJingleContent *content; }; static void gabble_call_stream_init (GabbleCallStream *self) { GabbleCallStreamPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GABBLE_TYPE_CALL_STREAM, GabbleCallStreamPrivate); self->priv = priv; } static void gabble_call_stream_dispose (GObject *object); static void gabble_call_stream_finalize (GObject *object); static GPtrArray * get_stun_servers (GabbleCallStream *self) { GPtrArray *arr; WockyJingleFactory *jf; GList *stun_servers; arr = g_ptr_array_new_with_free_func ((GDestroyNotify) g_value_array_free); jf = wocky_jingle_session_get_factory (self->priv->content->session); stun_servers = wocky_jingle_info_get_stun_servers ( wocky_jingle_factory_get_jingle_info (jf)); while (stun_servers != NULL) { WockyStunServer *stun_server = stun_servers->data; GValueArray *va = tp_value_array_build (2, G_TYPE_STRING, stun_server->address, G_TYPE_UINT, (guint) stun_server->port, G_TYPE_INVALID); g_ptr_array_add (arr, va); stun_servers = g_list_delete_link (stun_servers, stun_servers); } return arr; } static void gabble_call_stream_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GabbleCallStream *stream = GABBLE_CALL_STREAM (object); GabbleCallStreamPrivate *priv = stream->priv; switch (property_id) { case PROP_JINGLE_CONTENT: g_value_set_object (value, priv->content); break; case PROP_CAN_REQUEST_RECEIVING: { WockyJingleDialect dialect = wocky_jingle_session_get_dialect (priv->content->session); g_value_set_boolean (value, !WOCKY_JINGLE_DIALECT_IS_GOOGLE (dialect)); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gabble_call_stream_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GabbleCallStream *stream = GABBLE_CALL_STREAM (object); GabbleCallStreamPrivate *priv = stream->priv; switch (property_id) { case PROP_JINGLE_CONTENT: priv->content = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void google_relay_session_cb (GPtrArray *relays, gpointer user_data) { TpWeakRef *weak_ref = user_data; TpBaseMediaCallStream *stream = TP_BASE_MEDIA_CALL_STREAM ( tp_weak_ref_dup_object (weak_ref)); if (stream != NULL) { GPtrArray *tp_relays = gabble_build_tp_relay_info (relays); tp_base_media_call_stream_set_relay_info (stream, tp_relays); g_ptr_array_unref (tp_relays); g_object_unref (stream); } tp_weak_ref_destroy (weak_ref); } static void content_state_changed_cb (WockyJingleContent *content, GParamSpec *spec, gpointer user_data) { GabbleCallStream *self = GABBLE_CALL_STREAM (user_data); gabble_call_stream_update_member_states (self); } static void content_remote_members_changed_cb (WockyJingleContent *content, GParamSpec *spec, gpointer user_data) { GabbleCallStream *self = GABBLE_CALL_STREAM (user_data); gabble_call_stream_update_member_states (self); } static void jingle_info_stun_server_changed_cb ( WockyJingleInfo *jingle_info, const gchar *stun_server, guint stun_port, GabbleCallStream *self) { GPtrArray *stun_servers = get_stun_servers (self); tp_base_media_call_stream_set_stun_servers ( TP_BASE_MEDIA_CALL_STREAM (self), stun_servers); g_ptr_array_unref (stun_servers); } static void _new_candidates_cb ( WockyJingleContent *content, GList *candidates, TpCallStreamEndpoint *endpoint) { GPtrArray *tp_candidates; gchar *ufrag, *pwd; if (candidates == NULL) return; if (wocky_jingle_content_get_credentials (content, &ufrag, &pwd)) tp_call_stream_endpoint_set_remote_credentials (endpoint, ufrag, pwd); tp_candidates = gabble_call_candidates_to_array (candidates); tp_call_stream_endpoint_add_new_candidates (endpoint, tp_candidates); g_boxed_free (TP_ARRAY_TYPE_CANDIDATE_LIST, tp_candidates); } static void _endpoint_state_changed_cb ( TpCallStreamEndpoint *endpoint, GParamSpec *spec, WockyJingleContent *content) { TpMediaStreamState state; /* We only care about connecting RTP, RTCP is optional */ state = tp_call_stream_endpoint_get_state (endpoint, 1); wocky_jingle_content_set_transport_state (content, state); } static TpCallStreamEndpoint * _hook_up_endpoint (GabbleCallStream *self, const gchar *path, WockyJingleContent *content) { TpBaseCallStream *base = (TpBaseCallStream *) self; TpBaseConnection *conn = tp_base_call_stream_get_connection (base); TpDBusDaemon *bus = tp_base_connection_get_dbus_daemon (conn); TpCallStreamEndpoint *endpoint; TpStreamTransportType type = 0; GPtrArray *tp_candidates; GList *candidates; gchar *ufrag, *pwd; switch (wocky_jingle_content_get_transport_type (content)) { case JINGLE_TRANSPORT_GOOGLE_P2P: type = TP_STREAM_TRANSPORT_TYPE_GTALK_P2P; break; case JINGLE_TRANSPORT_RAW_UDP: type = TP_STREAM_TRANSPORT_TYPE_RAW_UDP; break; case JINGLE_TRANSPORT_ICE_UDP: type = TP_STREAM_TRANSPORT_TYPE_ICE; break; case JINGLE_TRANSPORT_UNKNOWN: default: g_assert_not_reached (); } /* FIXME: ice??? */ endpoint = tp_call_stream_endpoint_new (bus, path, type, FALSE); if (wocky_jingle_content_get_credentials (content, &ufrag, &pwd)) tp_call_stream_endpoint_set_remote_credentials (endpoint, ufrag, pwd); candidates = wocky_jingle_content_get_remote_candidates (content); tp_candidates = gabble_call_candidates_to_array (candidates); tp_call_stream_endpoint_add_new_candidates (endpoint, tp_candidates); g_boxed_free (TP_ARRAY_TYPE_CANDIDATE_LIST, tp_candidates); tp_g_signal_connect_object (content, "new-candidates", G_CALLBACK (_new_candidates_cb), endpoint, 0); tp_g_signal_connect_object (endpoint, "notify::endpoint-state", G_CALLBACK(_endpoint_state_changed_cb), content, 0); return endpoint; } static void gabble_call_stream_constructed (GObject *obj) { GabbleCallStream *self = GABBLE_CALL_STREAM (obj); TpBaseCallStream *base = (TpBaseCallStream *) self; TpBaseMediaCallStream *media_base = (TpBaseMediaCallStream *) self; GabbleCallStreamPrivate *priv = self->priv; GabbleConnection *conn; TpCallStreamEndpoint *endpoint; gchar *path; WockyJingleTransportType transport; GPtrArray *stun_servers; gboolean locally_created; if (G_OBJECT_CLASS (gabble_call_stream_parent_class)->constructed != NULL) G_OBJECT_CLASS (gabble_call_stream_parent_class)->constructed (obj); conn = GABBLE_CONNECTION (tp_base_call_stream_get_connection (base)); g_object_get (priv->content, "locally-created", &locally_created, NULL); if (locally_created && wocky_jingle_content_sending (priv->content)) tp_base_media_call_stream_set_local_sending ( TP_BASE_MEDIA_CALL_STREAM (self), TRUE); /* Currently we'll only have one endpoint we know right away */ path = g_strdup_printf ("%s/Endpoint", tp_base_call_stream_get_object_path (base)); endpoint = _hook_up_endpoint (self, path, priv->content); tp_base_media_call_stream_add_endpoint (media_base, endpoint); g_object_unref (endpoint); g_free (path); transport = wocky_jingle_content_get_transport_type (priv->content); if (transport == JINGLE_TRANSPORT_GOOGLE_P2P) { DEBUG ("Attempting to create Google relay session"); /* See if our server is Google, and if it is, ask them for a relay. * We ask for enough relays for 2 components (RTP and RTCP) since we * don't yet know whether there will be RTCP. */ wocky_jingle_info_create_google_relay_session ( gabble_jingle_mint_get_info (conn->jingle_mint), 2, google_relay_session_cb, tp_weak_ref_new (self, NULL, NULL)); } else { GPtrArray *relays = g_ptr_array_new (); tp_base_media_call_stream_set_relay_info (media_base, relays); g_ptr_array_unref (relays); } stun_servers = get_stun_servers (self); tp_base_media_call_stream_set_stun_servers ( TP_BASE_MEDIA_CALL_STREAM (self), stun_servers); g_ptr_array_unref (stun_servers); gabble_call_stream_update_member_states (GABBLE_CALL_STREAM (obj)); gabble_signal_connect_weak (priv->content, "notify::state", G_CALLBACK (content_state_changed_cb), obj); gabble_signal_connect_weak (priv->content, "notify::senders", G_CALLBACK (content_remote_members_changed_cb), obj); gabble_signal_connect_weak ( gabble_jingle_mint_get_info (conn->jingle_mint), "stun-server-changed", G_CALLBACK (jingle_info_stun_server_changed_cb), obj); } void gabble_call_stream_update_member_states (GabbleCallStream *self) { TpBaseCallStream *base = TP_BASE_CALL_STREAM (self); TpBaseMediaCallStream *bmcs = TP_BASE_MEDIA_CALL_STREAM (self); GabbleCallStreamPrivate *priv = self->priv; WockyJingleContentState state; TpSendingState local_state; TpSendingState remote_state; TpBaseConnection *conn = tp_base_call_stream_get_connection (base); TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( conn, TP_HANDLE_TYPE_CONTACT); TpHandle peer; g_object_get (priv->content, "state", &state, NULL); if (state == WOCKY_JINGLE_CONTENT_STATE_REMOVING) return; local_state = tp_base_call_stream_get_local_sending_state (base); remote_state = tp_base_call_stream_get_remote_sending_state (base, 0); if (wocky_jingle_content_sending (priv->content)) { if (tp_base_media_call_stream_get_local_sending (bmcs)) local_state = TP_SENDING_STATE_SENDING; else local_state = TP_SENDING_STATE_PENDING_SEND; } else { if (tp_base_media_call_stream_get_local_sending (bmcs)) local_state = TP_SENDING_STATE_PENDING_STOP_SENDING; else local_state = TP_SENDING_STATE_NONE; } if (wocky_jingle_content_receiving (priv->content)) { remote_state = TP_SENDING_STATE_SENDING; } else { remote_state = TP_SENDING_STATE_NONE; } DEBUG ("State: %d Local: %d Remote: %d", state, local_state, remote_state); tp_base_call_stream_update_local_sending_state (base, local_state, 0, TP_CALL_STATE_CHANGE_REASON_PROGRESS_MADE, "", ""); peer = tp_handle_ensure (contact_repo, wocky_jingle_session_get_peer_jid (priv->content->session), NULL, NULL); tp_base_call_stream_update_remote_sending_state (base, peer, remote_state, 0, TP_CALL_STATE_CHANGE_REASON_PROGRESS_MADE, "", ""); } static void gabble_call_stream_class_init (GabbleCallStreamClass *gabble_call_stream_class) { GObjectClass *object_class = G_OBJECT_CLASS (gabble_call_stream_class); TpBaseMediaCallStreamClass *bmcs_class = TP_BASE_MEDIA_CALL_STREAM_CLASS (gabble_call_stream_class); GParamSpec *param_spec; g_type_class_add_private (gabble_call_stream_class, sizeof (GabbleCallStreamPrivate)); object_class->set_property = gabble_call_stream_set_property; object_class->get_property = gabble_call_stream_get_property; object_class->dispose = gabble_call_stream_dispose; object_class->finalize = gabble_call_stream_finalize; object_class->constructed = gabble_call_stream_constructed; param_spec = g_param_spec_object ("jingle-content", "Jingle Content", "The Jingle Content related to this content object", WOCKY_TYPE_JINGLE_CONTENT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_JINGLE_CONTENT, param_spec); g_object_class_override_property (object_class, PROP_CAN_REQUEST_RECEIVING, "can-request-receiving"); bmcs_class->add_local_candidates = gabble_call_stream_add_candidates; bmcs_class->set_sending = gabble_call_stream_set_sending; bmcs_class->request_receiving = gabble_call_stream_request_receiving; } void gabble_call_stream_dispose (GObject *object) { GabbleCallStream *self = GABBLE_CALL_STREAM (object); GabbleCallStreamPrivate *priv = self->priv; if (priv->dispose_has_run) return; priv->dispose_has_run = TRUE; tp_clear_object (&priv->content); if (G_OBJECT_CLASS (gabble_call_stream_parent_class)->dispose) G_OBJECT_CLASS (gabble_call_stream_parent_class)->dispose (object); } void gabble_call_stream_finalize (GObject *object) { G_OBJECT_CLASS (gabble_call_stream_parent_class)->finalize (object); } static GPtrArray * gabble_call_stream_add_candidates (TpBaseMediaCallStream *stream, const GPtrArray *candidates, GError **error) { GabbleCallStream *self = GABBLE_CALL_STREAM (stream); GabbleCallStreamPrivate *priv = self->priv; GPtrArray *accepted_candidates = g_ptr_array_sized_new (candidates->len); GList *l = NULL; guint i; for (i = 0; i < candidates->len ; i++) { GValueArray *va; WockyJingleCandidate *c; GHashTable *info; guint tptype; WockyJingleCandidateType type; /* borrowed strings, owned by other people. */ const gchar *username; const gchar *password; const gchar *foundation; va = g_ptr_array_index (candidates, i); info = g_value_get_boxed (va->values + 3); tptype = tp_asv_get_uint32 (info, "type", NULL); switch (tptype) { default: /* Anything else is local */ case TP_CALL_STREAM_CANDIDATE_TYPE_HOST: type = WOCKY_JINGLE_CANDIDATE_TYPE_LOCAL; break; case TP_CALL_STREAM_CANDIDATE_TYPE_SERVER_REFLEXIVE: type = WOCKY_JINGLE_CANDIDATE_TYPE_STUN; break; case TP_CALL_STREAM_CANDIDATE_TYPE_RELAY: type = WOCKY_JINGLE_CANDIDATE_TYPE_RELAY; break; } username = tp_asv_get_string (info, "username"); if (username == NULL) username = tp_base_media_call_stream_get_username (stream); password = tp_asv_get_string (info, "password"); if (password == NULL) password = tp_base_media_call_stream_get_password (stream); foundation = tp_asv_get_string (info, "foundation"); if (foundation == NULL) foundation = "1"; c = wocky_jingle_candidate_new ( /* transport protocol */ tp_asv_get_uint32 (info, "protocol", NULL), /* Candidate type */ type, /* id/foundation */ foundation, /* component */ g_value_get_uint (va->values + 0), /* ip */ g_value_get_string (va->values + 1), /* port */ g_value_get_uint (va->values + 2), /* generation */ 0, /* preference */ tp_asv_get_uint32 (info, "priority", NULL), /* username, password */ username, password, /* network */ 0); l = g_list_append (l, c); g_ptr_array_add (accepted_candidates, va); } wocky_jingle_content_add_candidates (priv->content, l); if (accepted_candidates->len == 0 && candidates->len != 0) { g_set_error_literal (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "All candidates had the wrong Type"); tp_clear_pointer (&accepted_candidates, g_ptr_array_unref); } return accepted_candidates; } static gboolean gabble_call_stream_set_sending (TpBaseMediaCallStream *stream, gboolean sending, GError **error) { GabbleCallStream *self = GABBLE_CALL_STREAM (stream); if (sending) tp_base_media_call_stream_set_local_sending (stream, TRUE); wocky_jingle_content_set_sending (self->priv->content, sending); return TRUE; } static void gabble_call_stream_request_receiving (TpBaseMediaCallStream *stream, TpHandle contact, gboolean receive) { GabbleCallStream *self = GABBLE_CALL_STREAM (stream); wocky_jingle_content_request_receiving (self->priv->content, receive); } WockyJingleContent * gabble_call_stream_get_jingle_content (GabbleCallStream *stream) { return stream->priv->content; }