6 * Copyright (C) 2010 Jakub Adam <jakub.adam@tieto.com>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 #include <libpurple/mediamanager.h>
29 #include <nice/agent.h>
34 #include "sipe-session.h"
35 #include "sipe-media.h"
36 #include "sipe-dialog.h"
37 #include "sipe-utils.h"
38 #include "sipe-common.h"
40 typedef enum sipe_call_state
{
47 struct _sipe_media_call
{
49 struct sip_session
*session
;
50 struct sip_dialog
*dialog
;
52 struct sipmsg
*invitation
;
53 GList
*remote_candidates
;
61 sipe_media_get_callid(sipe_media_call
*call
)
63 return call
->dialog
->callid
;
67 sipe_media_call_free(sipe_media_call
*call
)
70 sipe_utils_nameval_free(call
->sdp_attrs
);
72 sipmsg_free(call
->invitation
);
73 purple_media_codec_list_free(call
->remote_codecs
);
74 purple_media_candidate_list_free(call
->remote_candidates
);
80 sipe_media_parse_remote_codecs(const sipe_media_call
*call
)
86 while ((attr
= sipe_utils_nameval_find_instance(call
->sdp_attrs
, "a", i
++))) {
91 PurpleMediaCodec
*codec
;
93 if (!g_str_has_prefix(attr
, "rtpmap:"))
96 tokens
= g_strsplit_set(attr
+ 7, " /", 3);
99 codec_name
= tokens
[1];
100 clock_rate
= atoi(tokens
[2]);
102 codec
= purple_media_codec_new(id
, codec_name
, PURPLE_MEDIA_AUDIO
, clock_rate
);
103 codecs
= g_list_append(codecs
, codec
);
107 printf("REMOTE CODEC: %s\n",purple_media_codec_to_string(codec
));
114 codec_name_compare(PurpleMediaCodec
* codec1
, PurpleMediaCodec
* codec2
)
116 gchar
*name1
= purple_media_codec_get_encoding_name(codec1
);
117 gchar
*name2
= purple_media_codec_get_encoding_name(codec2
);
119 return g_strcmp0(name1
, name2
);
123 sipe_media_prune_remote_codecs(PurpleMedia
*media
, GList
*codecs
)
125 GList
*remote_codecs
= codecs
;
126 GList
*local_codecs
= purple_media_get_codecs(media
, "sipe-voice");
127 GList
*pruned_codecs
= NULL
;
129 while (remote_codecs
) {
130 PurpleMediaCodec
*c
= remote_codecs
->data
;
132 if (g_list_find_custom(local_codecs
, c
, (GCompareFunc
)codec_name_compare
)) {
133 pruned_codecs
= g_list_append(pruned_codecs
, c
);
134 remote_codecs
->data
= NULL
;
136 printf("Pruned codec %s\n", purple_media_codec_get_encoding_name(c
));
139 remote_codecs
= remote_codecs
->next
;
142 purple_media_codec_list_free(codecs
);
144 return pruned_codecs
;
148 sipe_media_parse_remote_candidates(sipe_media_call
*call
)
150 GSList
*sdp_attrs
= call
->sdp_attrs
;
151 PurpleMediaCandidate
*candidate
;
152 GList
*candidates
= NULL
;
156 gchar
* username
= NULL
;
157 gchar
* password
= NULL
;
159 while ((attr
= sipe_utils_nameval_find_instance(sdp_attrs
, "a", i
++))) {
160 const char ICE_UFRAG
[] = "ice-ufrag:";
161 const char ICE_PWD
[] = "ice-pwd:";
162 const char CANDIDATE
[] = "candidate:";
164 if (g_str_has_prefix(attr
, ICE_UFRAG
) && !username
) {
165 username
= g_strdup(attr
+ sizeof (ICE_UFRAG
) - 1);
166 } else if (g_str_has_prefix(attr
, ICE_PWD
) && !password
) {
167 password
= g_strdup(attr
+ sizeof (ICE_PWD
) - 1);
168 } else if (g_str_has_prefix(attr
, CANDIDATE
)) {
171 PurpleMediaComponentType component
;
172 PurpleMediaNetworkProtocol protocol
;
176 PurpleMediaCandidateType type
;
178 tokens
= g_strsplit_set(attr
+ sizeof (CANDIDATE
) - 1, " ", 0);
180 foundation
= tokens
[0];
182 switch (atoi(tokens
[1])) {
184 component
= PURPLE_MEDIA_COMPONENT_RTP
;
187 component
= PURPLE_MEDIA_COMPONENT_RTCP
;
190 component
= PURPLE_MEDIA_COMPONENT_NONE
;
193 if (sipe_strequal(tokens
[2], "UDP"))
194 protocol
= PURPLE_MEDIA_NETWORK_PROTOCOL_UDP
;
196 // Ignore TCP candidates, at least for now...
201 priority
= atoi(tokens
[3]);
203 port
= atoi(tokens
[5]);
205 if (sipe_strequal(tokens
[7], "host"))
206 type
= PURPLE_MEDIA_CANDIDATE_TYPE_HOST
;
207 else if (sipe_strequal(tokens
[7], "relay"))
208 type
= PURPLE_MEDIA_CANDIDATE_TYPE_RELAY
;
209 else if (sipe_strequal(tokens
[7], "srflx"))
210 type
= PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX
;
216 candidate
= purple_media_candidate_new(foundation
, component
,
217 type
, protocol
, ip
, port
);
218 g_object_set(candidate
, "priority", priority
, NULL
);
219 candidates
= g_list_append(candidates
, candidate
);
226 // No a=candidate in SDP message, revert to OC2005 behaviour
227 gchar
**tokens
= g_strsplit(sipe_utils_nameval_find(sdp_attrs
, "o"), " ", 6);
228 gchar
*ip
= g_strdup(tokens
[5]);
233 tokens
= g_strsplit(sipe_utils_nameval_find(sdp_attrs
, "m"), " ", 3);
234 port
= atoi(tokens
[1]);
237 candidate
= purple_media_candidate_new("foundation",
238 PURPLE_MEDIA_COMPONENT_RTP
,
239 PURPLE_MEDIA_CANDIDATE_TYPE_HOST
,
240 PURPLE_MEDIA_NETWORK_PROTOCOL_UDP
, ip
, port
);
241 candidates
= g_list_append(candidates
, candidate
);
243 candidate
= purple_media_candidate_new("foundation",
244 PURPLE_MEDIA_COMPONENT_RTCP
,
245 PURPLE_MEDIA_CANDIDATE_TYPE_HOST
,
246 PURPLE_MEDIA_NETWORK_PROTOCOL_UDP
, ip
, port
+ 1);
247 candidates
= g_list_append(candidates
, candidate
);
249 // This seems to be pre-OC2007 R2 UAC
250 call
->legacy_mode
= TRUE
;
254 GList
*it
= candidates
;
256 g_object_set(it
->data
, "username", username
, "password", password
, NULL
);
268 sipe_media_sdp_codec_ids_format(GList
*codecs
)
270 GString
*result
= g_string_new(NULL
);
273 PurpleMediaCodec
*c
= codecs
->data
;
275 gchar
*tmp
= g_strdup_printf(" %d", purple_media_codec_get_id(c
));
276 g_string_append(result
,tmp
);
279 codecs
= codecs
->next
;
282 return g_string_free(result
, FALSE
);
286 sipe_media_sdp_codecs_format(GList
*codecs
)
288 GString
*result
= g_string_new(NULL
);
291 PurpleMediaCodec
*c
= codecs
->data
;
292 GList
*params
= NULL
;
294 gchar
*tmp
= g_strdup_printf("a=rtpmap:%d %s/%d\r\n",
295 purple_media_codec_get_id(c
),
296 purple_media_codec_get_encoding_name(c
),
297 purple_media_codec_get_clock_rate(c
));
299 g_string_append(result
, tmp
);
302 if ((params
= purple_media_codec_get_optional_parameters(c
))) {
303 tmp
= g_strdup_printf("a=fmtp:%d",purple_media_codec_get_id(c
));
304 g_string_append(result
, tmp
);
308 PurpleKeyValuePair
* par
= params
->data
;
309 tmp
= g_strdup_printf(" %s=%s", par
->key
, (gchar
*) par
->value
);
310 g_string_append(result
, tmp
);
312 params
= params
->next
;
314 g_string_append(result
, "\r\n");
317 codecs
= codecs
->next
;
320 return g_string_free(result
, FALSE
);
324 sipe_media_sdp_candidates_format(GList
*candidates
, sipe_media_call
* call
, gboolean remote_candidate
)
326 GString
*result
= g_string_new("");
328 gchar
*username
= purple_media_candidate_get_username(candidates
->data
);
329 gchar
*password
= purple_media_candidate_get_password(candidates
->data
);
330 guint16 rtcp_port
= 0;
332 if (call
->legacy_mode
)
333 return g_string_free(result
, FALSE
);
335 tmp
= g_strdup_printf("a=ice-ufrag:%s\r\na=ice-pwd:%s\r\n",username
, password
);
336 g_string_append(result
, tmp
);
340 PurpleMediaCandidate
*c
= candidates
->data
;
347 port
= purple_media_candidate_get_port(c
);
349 switch (purple_media_candidate_get_component_id(c
)) {
350 case PURPLE_MEDIA_COMPONENT_RTP
:
353 case PURPLE_MEDIA_COMPONENT_RTCP
:
360 switch (purple_media_candidate_get_protocol(c
)) {
361 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP
:
364 case PURPLE_MEDIA_NETWORK_PROTOCOL_UDP
:
369 switch (purple_media_candidate_get_candidate_type(c
)) {
370 case PURPLE_MEDIA_CANDIDATE_TYPE_HOST
:
373 case PURPLE_MEDIA_CANDIDATE_TYPE_RELAY
:
376 case PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX
:
380 // TODO: error unknown/unsupported type
384 tmp
= g_strdup_printf("a=candidate:%s %u %s %u %s %d typ %s \r\n",
385 purple_media_candidate_get_foundation(c
),
388 purple_media_candidate_get_priority(c
),
389 purple_media_candidate_get_ip(c
),
393 g_string_append(result
, tmp
);
396 candidates
= candidates
->next
;
399 if (remote_candidate
) {
400 PurpleMediaCandidate
*first
= call
->remote_candidates
->data
;
401 PurpleMediaCandidate
*second
= call
->remote_candidates
->next
->data
;
402 tmp
= g_strdup_printf("a=remote-candidates:1 %s %u 2 %s %u\r\n",
403 purple_media_candidate_get_ip(first
), purple_media_candidate_get_port(first
),
404 purple_media_candidate_get_ip(second
), purple_media_candidate_get_port(second
));
406 g_string_append(result
, tmp
);
411 if (rtcp_port
!= 0) {
412 tmp
= g_strdup_printf("a=maxptime:200\r\na=rtcp:%u\r\n", rtcp_port
);
413 g_string_append(result
, tmp
);
417 return g_string_free(result
, FALSE
);
421 sipe_media_create_sdp(sipe_media_call
*call
, gboolean remote_candidate
) {
422 PurpleMedia
*media
= call
->media
;
423 GList
*local_codecs
= purple_media_get_codecs(media
, "sipe-voice");
424 GList
*local_candidates
= purple_media_get_local_candidates(media
, "sipe-voice", call
->dialog
->with
);
426 // TODO: more sophisticated
427 guint16 local_port
= purple_media_candidate_get_port(local_candidates
->data
);
428 const char *ip
= sipe_utils_get_suitable_local_ip(-1);
430 gchar
*sdp_codecs
= sipe_media_sdp_codecs_format(local_codecs
);
431 gchar
*sdp_codec_ids
= sipe_media_sdp_codec_ids_format(local_codecs
);
432 gchar
*sdp_candidates
= sipe_media_sdp_candidates_format(local_candidates
, call
, remote_candidate
);
433 gchar
*inactive
= call
->state
== SIPE_CALL_HELD
? "a=inactive\r\n" : "";
435 gchar
*body
= g_strdup_printf(
437 "o=- 0 0 IN IP4 %s\r\n"
442 "m=audio %d RTP/AVP%s\r\n"
446 "a=encryption:rejected\r\n"
447 ,ip
, ip
, local_port
, sdp_codec_ids
, sdp_candidates
, inactive
, sdp_codecs
);
450 g_free(sdp_codec_ids
);
451 g_free(sdp_candidates
);
457 sipe_media_session_ready_cb(sipe_media_call
*call
)
459 PurpleMedia
*media
= call
->media
;
460 PurpleAccount
*account
= purple_media_get_account(media
);
462 if (!purple_media_candidates_prepared(media
, NULL
, NULL
))
465 if (!call
->sdp_response
)
466 call
->sdp_response
= sipe_media_create_sdp(call
, FALSE
);
468 if (!purple_media_accepted(media
, NULL
, NULL
)) {
469 if (!call
->legacy_mode
)
470 send_sip_response(account
->gc
, call
->invitation
, 183, "Session Progress", call
->sdp_response
);
472 send_sip_response(account
->gc
, call
->invitation
, 200, "OK", call
->sdp_response
);
473 call
->state
= SIPE_CALL_RUNNING
;
478 sipe_invite_call(struct sipe_account_data
*sip
)
483 sipe_media_call
*call
= sip
->media_call
;
484 struct sip_dialog
*dialog
= call
->dialog
;
486 contact
= get_contact(sip
);
487 hdr
= g_strdup_printf(
488 "Supported: ms-sender\r\n"
489 "ms-keep-alive: UAC;hop-hop=yes\r\n"
491 "Supported: Replaces\r\n"
492 "Content-Type: application/sdp\r\n",
494 call
->state
== SIPE_CALL_HELD
? ";+sip.rendering=\"no\"" : "");
497 body
= sipe_media_create_sdp(call
, TRUE
);
499 send_sip_request(sip
->gc
, "INVITE", dialog
->with
, dialog
->with
, hdr
, body
,
507 notify_state_change(struct sipe_account_data
*sip
, gboolean local
) {
509 sipe_invite_call(sip
);
511 gchar
* body
= sipe_media_create_sdp(sip
->media_call
, TRUE
);
512 send_sip_response(sip
->gc
, sip
->media_call
->invitation
, 200, "OK", body
);
518 sipe_media_stream_info_cb(PurpleMedia
*media
,
519 PurpleMediaInfoType type
,
520 SIPE_UNUSED_PARAMETER gchar
*sid
,
521 SIPE_UNUSED_PARAMETER gchar
*name
,
522 gboolean local
, struct sipe_account_data
*sip
)
524 sipe_media_call
*call
= sip
->media_call
;
526 if (type
== PURPLE_MEDIA_INFO_ACCEPT
)
527 sipe_media_session_ready_cb(call
);
528 else if (type
== PURPLE_MEDIA_INFO_REJECT
) {
529 PurpleAccount
*account
= purple_media_get_account(media
);
530 send_sip_response(account
->gc
, call
->invitation
, 603, "Decline", NULL
);
531 sipe_media_call_free(call
);
532 sip
->media_call
= NULL
;
533 } else if (type
== PURPLE_MEDIA_INFO_HOLD
) {
534 if (call
->state
== SIPE_CALL_HELD
)
537 call
->state
= SIPE_CALL_HELD
;
538 notify_state_change(sip
, local
);
539 purple_media_stream_info(media
, PURPLE_MEDIA_INFO_HOLD
, NULL
, NULL
, TRUE
);
541 } else if (type
== PURPLE_MEDIA_INFO_UNHOLD
) {
542 if (call
->state
== SIPE_CALL_RUNNING
)
545 call
->state
= SIPE_CALL_RUNNING
;
546 notify_state_change(sip
, local
);
548 purple_media_stream_info(media
, PURPLE_MEDIA_INFO_UNHOLD
, NULL
, NULL
, TRUE
);
549 } else if (type
== PURPLE_MEDIA_INFO_HANGUP
) {
550 call
->state
= SIPE_CALL_FINISHED
;
552 send_sip_request(sip
->gc
, "BYE", call
->dialog
->with
, call
->dialog
->with
,
553 NULL
, NULL
, call
->dialog
, NULL
);
554 sipe_media_call_free(call
);
555 sip
->media_call
= NULL
;
560 sipe_media_parse_sdp_frame(gchar
*frame
)
562 gchar
**lines
= g_strsplit(frame
, "\r\n", 0);
563 GSList
*sdp_attrs
= NULL
;
565 gboolean result
= sipe_utils_parse_lines(&sdp_attrs
, lines
, "=");
568 if (result
== FALSE
) {
569 sipe_utils_nameval_free(sdp_attrs
);
576 static struct sip_dialog
*
577 sipe_media_dialog_init(struct sip_session
* session
, struct sipmsg
*msg
)
579 gchar
*newTag
= gentag();
580 const gchar
*oldHeader
;
582 struct sip_dialog
*dialog
;
584 oldHeader
= sipmsg_find_header(msg
, "To");
585 newHeader
= g_strdup_printf("%s;tag=%s", oldHeader
, newTag
);
586 sipmsg_remove_header_now(msg
, "To");
587 sipmsg_add_header_now(msg
, "To", newHeader
);
590 dialog
= sipe_dialog_add(session
);
591 dialog
->callid
= g_strdup(session
->callid
);
592 dialog
->with
= parse_from(sipmsg_find_header(msg
, "From"));
593 sipe_dialog_parse(dialog
, msg
, FALSE
);
598 static sipe_media_call
*
599 sipe_media_call_init(struct sipmsg
*msg
)
601 sipe_media_call
*call
;
603 call
= g_new0(sipe_media_call
, 1);
604 call
->sdp_attrs
= sipe_media_parse_sdp_frame(msg
->body
);
605 call
->invitation
= msg
;
606 call
->legacy_mode
= FALSE
;
607 call
->state
= SIPE_CALL_CONNECTING
;
608 call
->remote_candidates
= sipe_media_parse_remote_candidates(call
);
612 void sipe_media_hold(struct sipe_account_data
*sip
) {
613 if (sip
->media_call
) {
614 purple_media_stream_info(sip
->media_call
->media
, PURPLE_MEDIA_INFO_HOLD
,
619 void sipe_media_unhold(struct sipe_account_data
*sip
) {
620 if (sip
->media_call
) {
621 purple_media_stream_info(sip
->media_call
->media
, PURPLE_MEDIA_INFO_UNHOLD
,
626 void sipe_media_incoming_invite(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
628 PurpleMediaManager
*manager
= purple_media_manager_get();
631 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
633 sipe_media_call
*call
;
634 struct sip_session
*session
;
635 struct sip_dialog
*dialog
;
639 if (sip
->media_call
) {
640 if (sipe_strequal(sip
->media_call
->dialog
->callid
, callid
)) {
645 call
= sip
->media_call
;
647 sipmsg_free(call
->invitation
);
648 msg
->dont_free
= TRUE
;
649 call
->invitation
= msg
;
651 sipmsg_add_header(msg
, "Supported", "Replaces");
653 sipe_utils_nameval_free(call
->sdp_attrs
);
654 call
->sdp_attrs
= NULL
;
655 call
->sdp_attrs
= sipe_media_parse_sdp_frame(msg
->body
);
657 if (call
->legacy_mode
&& call
->state
== SIPE_CALL_RUNNING
) {
658 sipe_media_hold(sip
);
662 while ((attr
= sipe_utils_nameval_find_instance(call
->sdp_attrs
, "a", i
++))) {
663 if (g_str_has_prefix("inactive", attr
)) {
664 sipe_media_hold(sip
);
669 if (call
->state
== SIPE_CALL_HELD
) {
670 sipe_media_unhold(sip
);
674 call
->remote_codecs
= sipe_media_parse_remote_codecs(call
);
675 call
->remote_codecs
= sipe_media_prune_remote_codecs(call
->media
, call
->remote_codecs
);
676 if (!call
->remote_codecs
) {
677 // TODO: error no remote codecs
679 if (purple_media_set_remote_codecs(call
->media
, "sipe-voice", call
->dialog
->with
,
680 call
->remote_codecs
) == FALSE
)
681 printf("ERROR SET REMOTE CODECS"); // TODO
683 rsp
= sipe_media_create_sdp(sip
->media_call
, TRUE
);
684 send_sip_response(sip
->gc
, msg
, 200, "OK", rsp
);
687 // TODO: send Busy Here
688 printf("MEDIA SESSION ALREADY IN PROGRESS");
693 call
= sipe_media_call_init(msg
);
695 session
= sipe_session_find_or_add_chat_by_callid(sip
, callid
);
696 dialog
= sipe_media_dialog_init(session
, msg
);
698 media
= purple_media_manager_create_media(manager
, sip
->account
,
699 "fsrtpconference", dialog
->with
, FALSE
);
701 g_signal_connect(G_OBJECT(media
), "stream-info",
702 G_CALLBACK(sipe_media_stream_info_cb
), sip
);
703 g_signal_connect_swapped(G_OBJECT(media
), "candidates-prepared",
704 G_CALLBACK(sipe_media_session_ready_cb
), call
);
707 call
->session
= session
;
708 call
->dialog
= dialog
;
711 if (call
->legacy_mode
) {
712 purple_media_add_stream(media
, "sipe-voice", dialog
->with
,
713 PURPLE_MEDIA_AUDIO
, FALSE
, "rawudp", 0, NULL
);
715 params
= g_new0(GParameter
, 2);
716 params
[0].name
= "controlling-mode";
717 g_value_init(¶ms
[0].value
, G_TYPE_BOOLEAN
);
718 g_value_set_boolean(¶ms
[0].value
, FALSE
);
719 params
[1].name
= "compatibility-mode";
720 g_value_init(¶ms
[1].value
, G_TYPE_UINT
);
721 g_value_set_uint(¶ms
[1].value
, NICE_COMPATIBILITY_OC2007R2
);
724 purple_media_add_stream(media
, "sipe-voice", dialog
->with
,
725 PURPLE_MEDIA_AUDIO
, FALSE
, "nice", 2, params
);
728 purple_media_add_remote_candidates(media
, "sipe-voice", dialog
->with
,
729 call
->remote_candidates
);
731 call
->remote_codecs
= sipe_media_parse_remote_codecs(call
);
732 call
->remote_codecs
= sipe_media_prune_remote_codecs(media
, call
->remote_codecs
);
733 if (!call
->remote_candidates
|| !call
->remote_codecs
) {
734 sipe_media_call_free(call
);
735 sip
->media_call
= NULL
;
736 printf("ERROR NO CANDIDATES OR CODECS");
739 if (purple_media_set_remote_codecs(media
, "sipe-voice", dialog
->with
,
740 call
->remote_codecs
) == FALSE
)
741 printf("ERROR SET REMOTE CODECS"); // TODO
743 sip
->media_call
= call
;
745 // TODO: copy message instead of this don't free thing
746 msg
->dont_free
= TRUE
;
747 send_sip_response(sip
->gc
, msg
, 180, "Ringing", NULL
);
750 void sipe_media_hangup(struct sipe_account_data
*sip
)
752 if (sip
->media_call
) {
753 purple_media_stream_info(sip
->media_call
->media
, PURPLE_MEDIA_INFO_HANGUP
,