Mercurial > pidgin.yaz
diff libpurple/protocols/jabber/jingle.c @ 25666:e73b03097664
Moved Jingle message handlers from jabber.c to jingle.c.
author | Mike Ruprecht <maiku@soc.pidgin.im> |
---|---|
date | Mon, 02 Jun 2008 20:59:20 +0000 |
parents | d048100a43ab |
children | 41d6d4217d21 a7be3074923b |
line wrap: on
line diff
--- a/libpurple/protocols/jabber/jingle.c Mon Jun 02 18:18:58 2008 +0000 +++ b/libpurple/protocols/jabber/jingle.c Mon Jun 02 20:59:20 2008 +0000 @@ -21,6 +21,7 @@ #include "purple.h" #include "jingle.h" #include "xmlnode.h" +#include "iq.h" #include <stdlib.h> #include <string.h> @@ -487,4 +488,492 @@ return jingle; } +static void +jabber_jingle_session_send_content_accept(JingleSession *session) +{ + JabberIq *result = jabber_iq_new(jabber_jingle_session_get_js(session), + JABBER_IQ_SET); + xmlnode *jingle = jabber_jingle_session_create_content_accept(session); + xmlnode_set_attrib(result->node, "to", + jabber_jingle_session_get_remote_jid(session)); + + xmlnode_insert_child(result->node, jingle); + jabber_iq_send(result); +} + +static void +jabber_jingle_session_send_session_accept(JingleSession *session) +{ + JabberIq *result = jabber_iq_new(jabber_jingle_session_get_js(session), + JABBER_IQ_SET); + xmlnode *jingle = jabber_jingle_session_create_session_accept(session); + xmlnode_set_attrib(result->node, "to", + jabber_jingle_session_get_remote_jid(session)); + + xmlnode_insert_child(result->node, jingle); + jabber_iq_send(result); + purple_debug_info("jabber", "Sent session accept, starting stream\n"); + gst_element_set_state(purple_media_get_audio_pipeline(session->media), GST_STATE_PLAYING); + + session->session_started = TRUE; +} + +static void +jabber_jingle_session_send_session_reject(JingleSession *session) +{ + JabberIq *result = jabber_iq_new(jabber_jingle_session_get_js(session), + JABBER_IQ_SET); + xmlnode *jingle = jabber_jingle_session_create_terminate(session, + "decline", NULL); + xmlnode_set_attrib(result->node, "to", + jabber_jingle_session_get_remote_jid(session)); + xmlnode_insert_child(result->node, jingle); + jabber_iq_send(result); + jabber_jingle_session_destroy(session); +} + +static void +jabber_jingle_session_send_session_terminate(JingleSession *session) +{ + JabberIq *result = jabber_iq_new(jabber_jingle_session_get_js(session), + JABBER_IQ_SET); + xmlnode *jingle = jabber_jingle_session_create_terminate(session, + "no-error", NULL); + xmlnode_set_attrib(result->node, "to", + jabber_jingle_session_get_remote_jid(session)); + xmlnode_insert_child(result->node, jingle); + jabber_iq_send(result); + jabber_jingle_session_destroy(session); +} + +/* callback called when new local transport candidate(s) are available on the + Farsight stream */ +static void +jabber_jingle_session_candidates_prepared(PurpleMedia *media, JingleSession *session) +{ + if (!jabber_jingle_session_is_initiator(session)) { + /* create transport-info package */ + JabberIq *result = jabber_iq_new(jabber_jingle_session_get_js(session), + JABBER_IQ_SET); + xmlnode *jingle = jabber_jingle_session_create_transport_info(session); + purple_debug_info("jabber", "jabber_session_candidates_prepared: %d candidates\n", + g_list_length(purple_media_get_local_audio_candidates(session->media))); + xmlnode_set_attrib(result->node, "to", + jabber_jingle_session_get_remote_jid(session)); + + xmlnode_insert_child(result->node, jingle); + jabber_iq_send(result); + } +} + +/* callback called when a pair of transport candidates (local and remote) + has been established */ +static void +jabber_jingle_session_candidate_pair_established(PurpleMedia *media, + FsCandidate *native_candidate, + FsCandidate *remote_candidate, + JingleSession *session) +{ + purple_debug_info("jabber", "jabber_candidate_pair_established called\n"); + /* if we are the initiator, we should send a content-modify message */ + if (jabber_jingle_session_is_initiator(session)) { + JabberIq *result; + xmlnode *jingle; + + purple_debug_info("jabber", "we are the initiator, let's send content-modify\n"); + + result = jabber_iq_new(jabber_jingle_session_get_js(session), JABBER_IQ_SET); + + /* shall change this to a "content-replace" */ + jingle = jabber_jingle_session_create_content_replace(session, + native_candidate, + remote_candidate); + xmlnode_set_attrib(result->node, "to", + jabber_jingle_session_get_remote_jid(session)); + xmlnode_insert_child(result->node, jingle); + jabber_iq_send(result); + } +} + +static gboolean +jabber_jingle_session_initiate_media_internal(JingleSession *session, + const char *initiator, + const char *remote_jid) +{ + PurpleMedia *media = NULL; + + media = purple_media_manager_create_media(purple_media_manager_get(), + session->js->gc, "fsrtpconference", remote_jid); + + if (!media) { + purple_debug_error("jabber", "Couldn't create fsrtpconference\n"); + return FALSE; + } + + /* this will need to be changed to "nice" once the libnice transmitter is finished */ + if (!purple_media_add_stream(media, remote_jid, PURPLE_MEDIA_AUDIO, "rawudp")) { + purple_debug_error("jabber", "Couldn't create audio stream\n"); + purple_media_reject(media); + return FALSE; + } + + jabber_jingle_session_set_remote_jid(session, remote_jid); + jabber_jingle_session_set_initiator(session, initiator); + jabber_jingle_session_set_media(session, media); + + /* connect callbacks */ + g_signal_connect_swapped(G_OBJECT(media), "accepted", + G_CALLBACK(jabber_jingle_session_send_session_accept), session); + g_signal_connect_swapped(G_OBJECT(media), "reject", + G_CALLBACK(jabber_jingle_session_send_session_reject), session); + g_signal_connect_swapped(G_OBJECT(media), "hangup", + G_CALLBACK(jabber_jingle_session_send_session_terminate), session); + g_signal_connect(G_OBJECT(media), "candidates-prepared", + G_CALLBACK(jabber_jingle_session_candidates_prepared), session); + g_signal_connect(G_OBJECT(media), "candidate-pair", + G_CALLBACK(jabber_jingle_session_candidate_pair_established), session); + + purple_media_ready(media); + + return TRUE; +} + +static void +jabber_jingle_session_initiate_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +{ + const char *from = xmlnode_get_attrib(packet, "from"); + JingleSession *session = jabber_jingle_session_find_by_jid(js, from); + PurpleMedia *media = session->media; + JabberIq *result; + xmlnode *jingle; + + if (!strcmp(xmlnode_get_attrib(packet, "type"), "error")) { + purple_media_got_hangup(media); + return; + } + + /* catch errors */ + if (xmlnode_get_child(packet, "error")) { + purple_media_got_hangup(media); + return; + } + + /* create transport-info package */ + result = jabber_iq_new(jabber_jingle_session_get_js(session), JABBER_IQ_SET); + jingle = jabber_jingle_session_create_transport_info(session); + purple_debug_info("jabber", "jabber_session_candidates_prepared: %d candidates\n", + g_list_length(purple_media_get_local_audio_candidates(session->media))); + xmlnode_set_attrib(result->node, "to", + jabber_jingle_session_get_remote_jid(session)); + + xmlnode_insert_child(result->node, jingle); + jabber_iq_send(result); +} + +PurpleMedia * +jabber_jingle_session_initiate_media(PurpleConnection *gc, const char *who, + PurpleMediaStreamType type) +{ + /* create content negotiation */ + JabberStream *js = gc->proto_data; + JabberIq *request = jabber_iq_new(js, JABBER_IQ_SET); + xmlnode *jingle, *content, *description, *transport; + GList *codecs; + JingleSession *session; + JabberBuddy *jb; + JabberBuddyResource *jbr; + + char *jid = NULL, *me = NULL; + + /* construct JID to send to */ + jb = jabber_buddy_find(js, who, FALSE); + if (!jb) { + purple_debug_error("jabber", "Could not find Jabber buddy\n"); + return NULL; + } + jbr = jabber_buddy_find_resource(jb, NULL); + if (!jbr) { + purple_debug_error("jabber", "Could not find buddy's resource\n"); + } + + if ((strchr(who, '/') == NULL) && jbr && (jbr->name != NULL)) { + jid = g_strdup_printf("%s/%s", who, jbr->name); + } else { + jid = g_strdup(who); + } + + session = jabber_jingle_session_create(js); + /* set ourselves as initiator */ + me = g_strdup_printf("%s@%s/%s", js->user->node, js->user->domain, js->user->resource); + + if (!jabber_jingle_session_initiate_media_internal(session, me, jid)) { + g_free(jid); + g_free(me); + jabber_jingle_session_destroy(session); + return NULL; + } + + g_free(jid); + g_free(me); + + codecs = purple_media_get_local_audio_codecs(session->media); + + /* create request */ + + xmlnode_set_attrib(request->node, "to", + jabber_jingle_session_get_remote_jid(session)); + jingle = xmlnode_new_child(request->node, "jingle"); + xmlnode_set_namespace(jingle, "urn:xmpp:tmp:jingle"); + xmlnode_set_attrib(jingle, "action", "session-initiate"); + /* get our JID and a session id... */ + xmlnode_set_attrib(jingle, "initiator", jabber_jingle_session_get_initiator(session)); + xmlnode_set_attrib(jingle, "sid", jabber_jingle_session_get_id(session)); + + content = xmlnode_new_child(jingle, "content"); + xmlnode_set_attrib(content, "name", "audio-content"); + xmlnode_set_attrib(content, "profile", "RTP/AVP"); + + description = jabber_jingle_session_create_description(session); + xmlnode_insert_child(content, description); + + transport = xmlnode_new_child(content, "transport"); + xmlnode_set_namespace(transport, "urn:xmpp:tmp:jingle:transports:ice-udp"); + + jabber_iq_set_callback(request, jabber_jingle_session_initiate_result_cb, NULL); + + /* send request to other part */ + jabber_iq_send(request); + + fs_codec_list_destroy(codecs); + + return session->media; +} + +void +jabber_jingle_session_handle_content_replace(JabberStream *js, xmlnode *packet) +{ + xmlnode *jingle = xmlnode_get_child(packet, "jingle"); + const char *sid = xmlnode_get_attrib(jingle, "sid"); + JingleSession *session = jabber_jingle_session_find_by_id(js, sid); + + if (!jabber_jingle_session_is_initiator(session) && session->session_started) { + JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT); + JabberIq *accept = jabber_iq_new(js, JABBER_IQ_SET); + xmlnode *content_accept = NULL; + + /* send acknowledement */ + xmlnode_set_attrib(result->node, "id", xmlnode_get_attrib(packet, "id")); + xmlnode_set_attrib(result->node, "to", xmlnode_get_attrib(packet, "from")); + jabber_iq_send(result); + + /* send content-accept */ + content_accept = jabber_jingle_session_create_content_accept(session); + xmlnode_set_attrib(accept->node, "id", xmlnode_get_attrib(packet, "id")); + xmlnode_set_attrib(accept->node, "to", xmlnode_get_attrib(packet, "from")); + xmlnode_insert_child(accept->node, content_accept); + + jabber_iq_send(accept); + } +} + +void +jabber_jingle_session_handle_session_accept(JabberStream *js, xmlnode *packet) +{ + JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT); + xmlnode *jingle = xmlnode_get_child(packet, "jingle"); + xmlnode *content = xmlnode_get_child(jingle, "content"); + const char *sid = xmlnode_get_attrib(jingle, "sid"); + const char *action = xmlnode_get_attrib(jingle, "action"); + JingleSession *session = jabber_jingle_session_find_by_id(js, sid); + GList *remote_codecs = NULL; + GList *remote_transports = NULL; + GList *codec_intersection; + FsCodec *top = NULL; + xmlnode *description = NULL; + xmlnode *transport = NULL; + + /* We should probably check validity of the incoming XML... */ + + xmlnode_set_attrib(result->node, "to", + jabber_jingle_session_get_remote_jid(session)); + jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); + + description = xmlnode_get_child(content, "description"); + transport = xmlnode_get_child(content, "transport"); + + /* fetch codecs from remote party */ + purple_debug_info("jabber", "get codecs from session-accept\n"); + remote_codecs = jabber_jingle_get_codecs(description); + purple_debug_info("jabber", "get transport candidates from session accept\n"); + remote_transports = jabber_jingle_get_candidates(transport); + + purple_debug_info("jabber", "Got %d codecs from responder\n", + g_list_length(remote_codecs)); + purple_debug_info("jabber", "Got %d transport candidates from responder\n", + g_list_length(remote_transports)); + + purple_debug_info("jabber", "Setting remote codecs on stream\n"); + + purple_media_set_remote_audio_codecs(session->media, + jabber_jingle_session_get_remote_jid(session), + remote_codecs); + + codec_intersection = purple_media_get_negotiated_audio_codecs(session->media); + purple_debug_info("jabber", "codec_intersection contains %d elems\n", + g_list_length(codec_intersection)); + /* get the top codec */ + if (g_list_length(codec_intersection) > 0) { + top = (FsCodec *) codec_intersection->data; + purple_debug_info("jabber", "Found a suitable codec on stream = %d\n", + top->id); + + /* we have found a suitable codec, but we will not start the stream + just yet, wait for transport negotiation to complete... */ + } + /* if we also got transport candidates, add them to our streams + list of known remote candidates */ + if (g_list_length(remote_transports) > 0) { + purple_media_add_remote_audio_candidates(session->media, + jabber_jingle_session_get_remote_jid(session), + remote_transports); + fs_candidate_list_destroy(remote_transports); + } + if (g_list_length(codec_intersection) == 0 && + g_list_length(remote_transports)) { + /* we didn't get any candidates and the codec intersection is empty, + this means this was not a content-accept message and we couldn't + find any suitable codecs, should return error and hang up */ + + } + + g_list_free(codec_intersection); + + if (!strcmp(action, "session-accept")) { + purple_media_got_accept(jabber_jingle_session_get_media(session)); + purple_debug_info("jabber", "Got session-accept, starting stream\n"); + gst_element_set_state(purple_media_get_audio_pipeline(session->media), + GST_STATE_PLAYING); + } + + jabber_iq_send(result); + + session->session_started = TRUE; +} + +void +jabber_jingle_session_handle_session_initiate(JabberStream *js, xmlnode *packet) +{ + JingleSession *session = NULL; + xmlnode *jingle = xmlnode_get_child(packet, "jingle"); + xmlnode *content = NULL; + xmlnode *description = NULL; + const char *sid = NULL; + const char *initiator = NULL; + GList *codecs = NULL; + JabberIq *result = NULL; + + if (!jingle) { + purple_debug_error("jabber", "Malformed request"); + return; + } + + sid = xmlnode_get_attrib(jingle, "sid"); + initiator = xmlnode_get_attrib(jingle, "initiator"); + + if (jabber_jingle_session_find_by_id(js, sid)) { + /* This should only happen if you start a session with yourself */ + purple_debug_error("jabber", "Jingle session with id={%s} already exists\n", sid); + return; + } + session = jabber_jingle_session_create_by_id(js, sid); + + /* init media */ + content = xmlnode_get_child(jingle, "content"); + if (!content) { + purple_debug_error("jabber", "jingle tag must contain content tag\n"); + /* should send error here */ + return; + } + + description = xmlnode_get_child(content, "description"); + + if (!description) { + purple_debug_error("jabber", "content tag must contain description tag\n"); + /* we should create an error iq here */ + return; + } + + if (!jabber_jingle_session_initiate_media_internal(session, initiator, initiator)) { + purple_debug_error("jabber", "Couldn't start media session with %s\n", initiator); + jabber_jingle_session_destroy(session); + /* we should create an error iq here */ + return; + } + + codecs = jabber_jingle_get_codecs(description); + + purple_media_set_remote_audio_codecs(session->media, initiator, codecs); + + result = jabber_iq_new(js, JABBER_IQ_RESULT); + jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); + xmlnode_set_attrib(result->node, "to", xmlnode_get_attrib(packet, "from")); + jabber_iq_send(result); +} + +void +jabber_jingle_session_handle_session_terminate(JabberStream *js, xmlnode *packet) +{ + JabberIq *result = jabber_iq_new(js, JABBER_IQ_SET); + xmlnode *jingle = xmlnode_get_child(packet, "jingle"); + const char *sid = xmlnode_get_attrib(jingle, "sid"); + JingleSession *session = jabber_jingle_session_find_by_id(js, sid); + + if (!session) { + purple_debug_error("jabber", "jabber_handle_session_terminate couldn't find session\n"); + return; + } + + xmlnode_set_attrib(result->node, "to", + jabber_jingle_session_get_remote_jid(session)); + xmlnode_set_attrib(result->node, "id", xmlnode_get_attrib(packet, "id")); + + + + /* maybe we should look at the reasoncode to determine if it was + a hangup or a reject, and call different callbacks to purple_media */ + gst_element_set_state(purple_media_get_audio_pipeline(session->media), GST_STATE_NULL); + + purple_media_got_hangup(jabber_jingle_session_get_media(session)); + jabber_iq_send(result); + jabber_jingle_session_destroy(session); +} + +void +jabber_jingle_session_handle_transport_info(JabberStream *js, xmlnode *packet) +{ + JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT); + xmlnode *jingle = xmlnode_get_child(packet, "jingle"); + xmlnode *content = xmlnode_get_child(jingle, "content"); + xmlnode *transport = xmlnode_get_child(content, "transport"); + GList *remote_candidates = jabber_jingle_get_candidates(transport); + const char *sid = xmlnode_get_attrib(jingle, "sid"); + JingleSession *session = jabber_jingle_session_find_by_id(js, sid); + + if (!session) + purple_debug_error("jabber", "jabber_handle_session_candidates couldn't find session\n"); + + /* send acknowledement */ + xmlnode_set_attrib(result->node, "id", xmlnode_get_attrib(packet, "id")); + xmlnode_set_attrib(result->node, "to", xmlnode_get_attrib(packet, "from")); + jabber_iq_send(result); + + /* add candidates to our list of remote candidates */ + if (g_list_length(remote_candidates) > 0) { + purple_media_add_remote_audio_candidates(session->media, + xmlnode_get_attrib(packet, "from"), + remote_candidates); + fs_candidate_list_destroy(remote_candidates); + } +} + #endif /* USE_VV */