comparison libpurple/protocols/jabber/google.c @ 23759:315151da0dc6

Basic Google Talk voice call support. No UI; receiving a call auto-accepts it.
author Sean Egan <seanegan@gmail.com>
date Wed, 05 Sep 2007 00:47:58 +0000
parents 0580b246b2c5
children 70cdff43ec76
comparison
equal deleted inserted replaced
23758:1e5d5d55a231 23759:315151da0dc6
16 * You should have received a copy of the GNU General Public License 16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software 17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */ 19 */
20 20
21 #include <farsight/farsight-transport.h>
22
21 #include "internal.h" 23 #include "internal.h"
22 #include "debug.h" 24 #include "debug.h"
25 #include "mediamanager.h"
23 #include "util.h" 26 #include "util.h"
24 #include "privacy.h" 27 #include "privacy.h"
25 28
26 #include "buddy.h" 29 #include "buddy.h"
27 #include "google.h" 30 #include "google.h"
28 #include "jabber.h" 31 #include "jabber.h"
29 #include "presence.h" 32 #include "presence.h"
30 #include "iq.h" 33 #include "iq.h"
34
35 typedef struct {
36 char *id;
37 char *initiator;
38 } GoogleSessionId;
39
40 typedef enum {
41 UNINIT,
42 SENT_INITIATE,
43 RECEIVED_INITIATE,
44 IN_PRORESS,
45 TERMINATED
46 } GoogleSessionState;
47
48 typedef struct {
49 GoogleSessionId id;
50 GoogleSessionState state;
51 PurpleMedia *media;
52 FarsightStream *stream;
53 JabberStream *js;
54 char *remote_jid;
55 } GoogleSession;
56
57
58 static guint
59 google_session_id_hash(gconstpointer key)
60 {
61 GoogleSessionId *id = (GoogleSessionId*)key;
62
63 guint id_hash = g_str_hash(id->id);
64 guint init_hash = g_str_hash(id->initiator);
65
66 return 23 * id_hash + init_hash;
67 }
68
69 static gboolean
70 google_session_id_equal(gconstpointer a, gconstpointer b)
71 {
72 GoogleSessionId *c = (GoogleSessionId*)a;
73 GoogleSessionId *d = (GoogleSessionId*)b;
74
75 return !strcmp(c->id, d->id) && !strcmp(c->initiator, d->initiator);
76 }
77
78 GHashTable *sessions = NULL;
79
80 static xmlnode *
81 google_session_create_xmlnode(GoogleSession *session, const char *type)
82 {
83 xmlnode *node = xmlnode_new("session");
84 xmlnode_set_namespace(node, "http://www.google.com/session");
85 xmlnode_set_attrib(node, "id", session->id.id);
86 xmlnode_set_attrib(node, "initiator", session->id.initiator);
87 xmlnode_set_attrib(node, "type", type);
88 return node;
89 }
90
91 static void
92 google_session_send_accept(GoogleSession *session)
93 {
94 xmlnode *sess, *desc, *payload;
95 GList *codecs = farsight_stream_get_codec_intersection(session->stream);
96 JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET);
97
98 xmlnode_set_attrib(iq->node, "to", session->remote_jid);
99 sess = google_session_create_xmlnode(session, "accept");
100 xmlnode_insert_child(iq->node, sess);
101 desc = xmlnode_new_child(sess, "description");
102 xmlnode_set_namespace(desc, "http://www.google.com/session/phone");
103
104 for (;codecs; codecs = codecs->next) {
105 FarsightCodec *codec = (FarsightCodec*)codecs->data;
106 char id[8], clockrate[10];
107 payload = xmlnode_new_child(desc, "payload-type");
108 g_snprintf(id, sizeof(id), "%d", codec->id);
109 g_snprintf(clockrate, sizeof(clockrate), "%d", codec->clock_rate);
110 xmlnode_set_attrib(payload, "name", codec->encoding_name);
111 xmlnode_set_attrib(payload, "id", id);
112 xmlnode_set_attrib(payload, "clockrate", clockrate);
113 }
114
115 jabber_iq_send(iq);
116 farsight_stream_start(session->stream);
117 }
118
119 static void
120 google_session_candidates_prepared (FarsightStream *stream, gchar *candidate_id, GoogleSession *session)
121 {
122 JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET);
123 GList *candidates = farsight_stream_get_native_candidate_list(stream);
124 FarsightTransportInfo *transport;
125 xmlnode *sess;
126 xmlnode *candidate;
127 sess = google_session_create_xmlnode(session, "candidates");
128 xmlnode_insert_child(iq->node, sess);
129 xmlnode_set_attrib(iq->node, "to", session->remote_jid);
130
131 for (;candidates;candidates = candidates->next) {
132 transport = (FarsightTransportInfo*)(candidates->data);
133 char port[8];
134 char pref[8];
135
136 if (!strcmp(transport->ip, "127.0.0.1"))
137 continue;
138
139 candidate = xmlnode_new("candidate");
140
141 g_snprintf(port, sizeof(port), "%d", transport->port);
142 g_snprintf(pref, sizeof(pref), "%f", transport->preference);
143
144 xmlnode_set_attrib(candidate, "address", transport->ip);
145 xmlnode_set_attrib(candidate, "port", port);
146 xmlnode_set_attrib(candidate, "name", "rtp");
147 xmlnode_set_attrib(candidate, "username", transport->username);
148 xmlnode_set_attrib(candidate, "password", transport->password);
149 xmlnode_set_attrib(candidate, "preference", pref);
150 xmlnode_set_attrib(candidate, "protocol", transport->proto == FARSIGHT_NETWORK_PROTOCOL_UDP ? "udp" : "tcp");
151 xmlnode_set_attrib(candidate, "type", transport->type == FARSIGHT_CANDIDATE_TYPE_LOCAL ? "local" :
152 transport->type == FARSIGHT_CANDIDATE_TYPE_DERIVED ? "stun" :
153 transport->type == FARSIGHT_CANDIDATE_TYPE_RELAY ? "relay" : NULL);
154 xmlnode_set_attrib(candidate, "generation", "0");
155 xmlnode_set_attrib(candidate, "network", "0");
156 xmlnode_insert_child(sess, candidate);
157
158 }
159 jabber_iq_send(iq);
160 }
161
162 static gboolean
163 google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *packet)
164 {
165 PurpleMedia *media;
166 FarsightSession *fs;
167 GList *codecs = NULL;
168 xmlnode *desc_element, *codec_element;
169 FarsightCodec *codec;
170 const char *id, *encoding_name, *clock_rate;
171 int res;
172
173
174 if (session->state != UNINIT) {
175 purple_debug_error("jabber", "Received initiate for active session.\n");
176 return FALSE;
177 }
178
179 fs = farsight_session_factory_make("rtp");
180 if (!fs) {
181 purple_debug_error("jabber", "Farsight's rtp plugin not installed");
182 return FALSE;
183 }
184 session->stream = farsight_session_create_stream(fs, FARSIGHT_MEDIA_TYPE_AUDIO, FARSIGHT_STREAM_DIRECTION_BOTH);
185
186 g_object_set(G_OBJECT(session->stream), "transmitter", "libjingle", NULL);
187
188 desc_element = xmlnode_get_child(packet, "description");
189
190 for (codec_element = xmlnode_get_child(desc_element, "payload-type");
191 codec_element;
192 codec_element = xmlnode_get_next_twin(codec_element)) {
193 encoding_name = xmlnode_get_attrib(codec_element, "name");
194 id = xmlnode_get_attrib(codec_element, "id");
195 clock_rate = xmlnode_get_attrib(codec_element, "clockrate");
196
197 codec = g_new0(FarsightCodec, 1);
198 farsight_codec_init(codec, atoi(id), encoding_name, FARSIGHT_MEDIA_TYPE_AUDIO, clock_rate ? atoi(clock_rate) : 0);
199 codecs = g_list_append(codecs, codec);
200 }
201 GstElement *e = gst_element_factory_make("alsasrc", "source");
202 farsight_stream_set_source(session->stream, e);
203 farsight_stream_set_source_filter(session->stream, gst_caps_new_simple("audio/x-raw-int", "rate",G_TYPE_INT,8000, NULL));
204 gst_object_unref(e);
205
206 e = gst_element_factory_make("alsasink", "fakes");
207 g_object_set(e, "sync", FALSE, NULL);
208 farsight_stream_set_sink(session->stream, e);
209 gst_object_unref(e);
210
211 farsight_stream_prepare_transports(session->stream);
212 res = farsight_stream_set_remote_codecs(session->stream, codecs);
213
214
215 farsight_codec_list_destroy(codecs);
216 g_signal_connect(G_OBJECT(session->stream), "new-native-candidate", G_CALLBACK(google_session_candidates_prepared), session);
217 google_session_send_accept(session);
218 media = purple_media_manager_create_media(purple_media_manager_get(), js->gc, session->remote_jid);
219 return res;
220 }
221
222 static gboolean
223 google_session_handle_candidates(JabberStream *js, GoogleSession *session, xmlnode *sess)
224 {
225 GList *list = NULL;
226 xmlnode *cand;
227 static int name = 0;
228 char n[4];
229
230 for (cand = xmlnode_get_child(sess, "candidate"); cand; cand = xmlnode_get_next_twin(cand)) {
231 FarsightTransportInfo *info = g_new0(FarsightTransportInfo, 1);
232 g_snprintf(n, sizeof(n), "S%d", name++);
233 info->ip = xmlnode_get_attrib(cand, "address");
234 info->port = atoi(xmlnode_get_attrib(cand, "port"));
235 info->proto = !strcmp(xmlnode_get_attrib(cand, "protocol"),"udp") ? FARSIGHT_NETWORK_PROTOCOL_UDP : FARSIGHT_NETWORK_PROTOCOL_TCP;
236 info->preference = atof(xmlnode_get_attrib(cand, "preference"));
237 info->type = !strcmp(xmlnode_get_attrib(cand, "type"), "local") ? FARSIGHT_CANDIDATE_TYPE_LOCAL :
238 !strcmp(xmlnode_get_attrib(cand, "type"), "stun") ? FARSIGHT_CANDIDATE_TYPE_DERIVED :
239 !strcmp(xmlnode_get_attrib(cand, "type"), "relay") ? FARSIGHT_CANDIDATE_TYPE_RELAY : FARSIGHT_CANDIDATE_TYPE_LOCAL;
240 info->candidate_id = n;
241 info->username = xmlnode_get_attrib(cand, "username");
242 info->password = xmlnode_get_attrib(cand, "password");
243 list = g_list_append(list, info);
244 }
245
246 farsight_stream_add_remote_candidate(session->stream, list);
247 g_list_foreach(list, g_free, NULL);
248 g_list_free(list);
249 return TRUE;
250 }
251
252 static void
253 google_session_parse_iq(JabberStream *js, GoogleSession *session, xmlnode *packet)
254 {
255 JabberIq *result;
256 gboolean valid = TRUE;
257 xmlnode *sess = xmlnode_get_child(packet, "session");
258 const char *type = xmlnode_get_attrib(sess, "type");
259
260 if (!strcmp(type, "initiate")) {
261 valid = google_session_handle_initiate(js, session, sess);
262 } else if (!strcmp(type, "accept")) {
263 } else if (!strcmp(type, "reject")) {
264 } else if (!strcmp(type, "terminate")) {
265 } else if (!strcmp(type, "candidates")) {
266 valid = google_session_handle_candidates(js, session, sess);
267 }
268
269 if (valid) {
270 result = jabber_iq_new(js, JABBER_IQ_RESULT);
271 jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
272 xmlnode_set_attrib(result->node, "to", session->remote_jid);
273 jabber_iq_send(result);
274 }
275 }
276
277 void
278 jabber_google_session_parse(JabberStream *js, xmlnode *packet)
279 {
280 GoogleSession *session;
281 GoogleSessionId id;
282 JabberIq *result;
283
284 xmlnode *session_node;
285 xmlnode *desc_node;
286
287 if (strcmp(xmlnode_get_attrib(packet, "type"), "set"))
288 return;
289
290 session_node = xmlnode_get_child(packet, "session");
291 if (!session_node)
292 return;
293
294 id.id = xmlnode_get_attrib(session_node, "id");
295 if (!id.id)
296 return;
297
298 id.initiator = xmlnode_get_attrib(session_node, "initiator");
299 if (!id.initiator)
300 return;
301
302 if (sessions == NULL)
303 sessions = g_hash_table_new(google_session_id_hash, google_session_id_equal);
304 session = (GoogleSession*)g_hash_table_lookup(sessions, &id);
305
306 if (session) {
307 google_session_parse_iq(js, session, packet);
308 return;
309 }
310
311 /* If the session doesn't exist, this has to be an initiate message */
312 if (strcmp(xmlnode_get_attrib(session_node, "type"), "initiate"))
313 return;
314 desc_node = xmlnode_get_child(session_node, "description");
315 if (!desc_node)
316 return;
317 session = g_new0(GoogleSession, 1);
318 session->id.id = g_strdup(id.id);
319 session->id.initiator = g_strdup(id.initiator);
320 session->state = UNINIT;
321 session->js = js;
322 session->remote_jid = g_strdup(session->id.initiator);
323 g_hash_table_insert(sessions, &(session->id), session);
324
325 google_session_parse_iq(js, session, packet);
326 }
31 327
32 static void 328 static void
33 jabber_gmail_parse(JabberStream *js, xmlnode *packet, gpointer nul) 329 jabber_gmail_parse(JabberStream *js, xmlnode *packet, gpointer nul)
34 { 330 {
35 const char *type = xmlnode_get_attrib(packet, "type"); 331 const char *type = xmlnode_get_attrib(packet, "type");