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
30 #include <libpurple/mediamanager.h>
31 #include <nice/agent.h>
33 #include "sipe-core.h"
36 #include "sipe-session.h"
37 #include "sipe-media.h"
38 #include "sipe-dialog.h"
39 #include "sipe-utils.h"
40 #include "sipe-common.h"
43 sipe_media_get_callid(sipe_media_call
*call
)
45 return call
->dialog
->callid
;
48 void sipe_media_codec_list_free(GList
*codecs
)
50 for (; codecs
; codecs
= g_list_delete_link(codecs
, codecs
)) {
51 sipe_backend_codec_free(codecs
->data
);
55 void sipe_media_candidate_list_free(GList
*candidates
)
57 for (; candidates
; candidates
= g_list_delete_link(candidates
, candidates
)) {
58 sipe_backend_candidate_free(candidates
->data
);
63 sipe_media_call_free(sipe_media_call
*call
)
66 sipe_utils_nameval_free(call
->sdp_attrs
);
68 sipmsg_free(call
->invitation
);
69 sipe_media_codec_list_free(call
->remote_codecs
);
70 sipe_media_candidate_list_free(call
->remote_candidates
);
76 sipe_media_parse_remote_codecs(const sipe_media_call
*call
)
82 while ((attr
= sipe_utils_nameval_find_instance(call
->sdp_attrs
, "rtpmap", i
++))) {
83 gchar
**tokens
= g_strsplit_set(attr
, " /", 3);
85 int id
= atoi(tokens
[0]);
86 gchar
*name
= tokens
[1];
87 int clock_rate
= atoi(tokens
[2]);
88 SipeMediaType type
= SIPE_MEDIA_AUDIO
;
90 sipe_codec
*codec
= sipe_backend_codec_new(id
, name
, clock_rate
, type
);
92 codecs
= g_list_append(codecs
, codec
);
100 codec_name_compare(sipe_codec
* codec1
, sipe_codec
* codec2
)
102 gchar
*name1
= sipe_backend_codec_get_name(codec1
);
103 gchar
*name2
= sipe_backend_codec_get_name(codec2
);
105 return g_strcmp0(name1
, name2
);
109 sipe_media_prune_remote_codecs(sipe_media_call
*call
, GList
*codecs
)
111 GList
*remote_codecs
= codecs
;
112 GList
*local_codecs
= sipe_backend_get_local_codecs(call
);
113 GList
*pruned_codecs
= NULL
;
115 while (remote_codecs
) {
116 sipe_codec
*c
= remote_codecs
->data
;
118 if (g_list_find_custom(local_codecs
, c
, (GCompareFunc
)codec_name_compare
)) {
119 pruned_codecs
= g_list_append(pruned_codecs
, c
);
120 remote_codecs
->data
= NULL
;
122 remote_codecs
= remote_codecs
->next
;
125 sipe_media_codec_list_free(codecs
);
127 return pruned_codecs
;
131 sipe_media_parse_remote_candidates(sipe_media_call
*call
)
133 GSList
*sdp_attrs
= call
->sdp_attrs
;
134 sipe_candidate
*candidate
;
135 GList
*candidates
= NULL
;
139 const gchar
* username
= sipe_utils_nameval_find(sdp_attrs
, "ice-ufrag");
140 const gchar
* password
= sipe_utils_nameval_find(sdp_attrs
, "ice-pwd");
142 while ((attr
= sipe_utils_nameval_find_instance(sdp_attrs
, "candidate", i
++))) {
145 SipeComponentType component
;
146 SipeNetworkProtocol protocol
;
150 SipeCandidateType type
;
152 tokens
= g_strsplit_set(attr
, " ", 0);
154 foundation
= tokens
[0];
156 switch (atoi(tokens
[1])) {
158 component
= SIPE_COMPONENT_RTP
;
161 component
= SIPE_COMPONENT_RTCP
;
164 component
= SIPE_COMPONENT_NONE
;
167 if (sipe_strequal(tokens
[2], "UDP"))
168 protocol
= SIPE_NETWORK_PROTOCOL_UDP
;
170 // Ignore TCP candidates, at least for now...
175 priority
= atoi(tokens
[3]);
177 port
= atoi(tokens
[5]);
179 if (sipe_strequal(tokens
[7], "host"))
180 type
= SIPE_CANDIDATE_TYPE_HOST
;
181 else if (sipe_strequal(tokens
[7], "relay"))
182 type
= SIPE_CANDIDATE_TYPE_RELAY
;
183 else if (sipe_strequal(tokens
[7], "srflx"))
184 type
= SIPE_CANDIDATE_TYPE_SRFLX
;
190 candidate
= sipe_backend_candidate_new(foundation
, component
,
191 type
, protocol
, ip
, port
);
192 g_object_set(candidate
, "priority", priority
, NULL
);
193 candidates
= g_list_append(candidates
, candidate
);
199 // No a=candidate in SDP message, revert to OC2005 behaviour
200 candidate
= sipe_backend_candidate_new("foundation",
202 SIPE_CANDIDATE_TYPE_HOST
,
203 SIPE_NETWORK_PROTOCOL_UDP
,
204 call
->remote_ip
, call
->remote_port
);
205 candidates
= g_list_append(candidates
, candidate
);
207 candidate
= sipe_backend_candidate_new("foundation",
209 SIPE_CANDIDATE_TYPE_HOST
,
210 SIPE_NETWORK_PROTOCOL_UDP
,
211 call
->remote_ip
, call
->remote_port
+ 1);
212 candidates
= g_list_append(candidates
, candidate
);
214 // This seems to be pre-OC2007 R2 UAC
215 call
->legacy_mode
= TRUE
;
219 GList
*it
= candidates
;
221 sipe_backend_candidate_set_username_and_pwd(it
->data
, username
, password
);
230 sipe_media_sdp_codec_ids_format(GList
*codecs
)
232 GString
*result
= g_string_new(NULL
);
235 sipe_codec
*c
= codecs
->data
;
237 gchar
*tmp
= g_strdup_printf(" %d", sipe_backend_codec_get_id(c
));
238 g_string_append(result
,tmp
);
241 codecs
= codecs
->next
;
244 return g_string_free(result
, FALSE
);
248 sipe_media_sdp_codecs_format(GList
*codecs
)
250 GString
*result
= g_string_new(NULL
);
253 sipe_codec
*c
= codecs
->data
;
254 GList
*params
= NULL
;
256 gchar
*tmp
= g_strdup_printf("a=rtpmap:%d %s/%d\r\n",
257 sipe_backend_codec_get_id(c
),
258 sipe_backend_codec_get_name(c
),
259 sipe_backend_codec_get_clock_rate(c
));
261 g_string_append(result
, tmp
);
264 if ((params
= sipe_backend_codec_get_optional_parameters(c
))) {
265 tmp
= g_strdup_printf("a=fmtp:%d",sipe_backend_codec_get_id(c
));
266 g_string_append(result
, tmp
);
270 struct sipnameval
* par
= params
->data
;
271 tmp
= g_strdup_printf(" %s=%s", par
->name
, par
->value
);
272 g_string_append(result
, tmp
);
274 params
= params
->next
;
276 g_string_append(result
, "\r\n");
279 codecs
= codecs
->next
;
282 return g_string_free(result
, FALSE
);
286 sipe_media_sdp_candidates_format(GList
*candidates
, sipe_media_call
* call
, gboolean remote_candidate
)
288 GString
*result
= g_string_new("");
290 gchar
*username
= sipe_backend_candidate_get_username(candidates
->data
);
291 gchar
*password
= sipe_backend_candidate_get_password(candidates
->data
);
292 guint16 rtcp_port
= 0;
294 if (call
->legacy_mode
)
295 return g_string_free(result
, FALSE
);
297 tmp
= g_strdup_printf("a=ice-ufrag:%s\r\na=ice-pwd:%s\r\n",username
, password
);
298 g_string_append(result
, tmp
);
302 sipe_candidate
*c
= candidates
->data
;
309 port
= sipe_backend_candidate_get_port(c
);
311 switch (sipe_backend_candidate_get_component_type(c
)) {
312 case SIPE_COMPONENT_RTP
:
315 case SIPE_COMPONENT_RTCP
:
320 case SIPE_COMPONENT_NONE
:
324 switch (sipe_backend_candidate_get_protocol(c
)) {
325 case SIPE_NETWORK_PROTOCOL_TCP
:
328 case SIPE_NETWORK_PROTOCOL_UDP
:
333 switch (sipe_backend_candidate_get_type(c
)) {
334 case SIPE_CANDIDATE_TYPE_HOST
:
337 case SIPE_CANDIDATE_TYPE_RELAY
:
340 case SIPE_CANDIDATE_TYPE_SRFLX
:
344 // TODO: error unknown/unsupported type
348 tmp
= g_strdup_printf("a=candidate:%s %u %s %u %s %d typ %s \r\n",
349 sipe_backend_candidate_get_foundation(c
),
352 sipe_backend_candidate_get_priority(c
),
353 sipe_backend_candidate_get_ip(c
),
357 g_string_append(result
, tmp
);
360 candidates
= candidates
->next
;
363 if (remote_candidate
) {
364 PurpleMediaCandidate
*first
= call
->remote_candidates
->data
;
365 PurpleMediaCandidate
*second
= call
->remote_candidates
->next
->data
;
366 tmp
= g_strdup_printf("a=remote-candidates:1 %s %u 2 %s %u\r\n",
367 purple_media_candidate_get_ip(first
), purple_media_candidate_get_port(first
),
368 purple_media_candidate_get_ip(second
), purple_media_candidate_get_port(second
));
370 g_string_append(result
, tmp
);
375 if (rtcp_port
!= 0) {
376 tmp
= g_strdup_printf("a=maxptime:200\r\na=rtcp:%u\r\n", rtcp_port
);
377 g_string_append(result
, tmp
);
381 return g_string_free(result
, FALSE
);
385 sipe_media_create_sdp(sipe_media_call
*call
, gboolean remote_candidate
) {
386 PurpleMedia
*media
= call
->media
;
387 GList
*local_codecs
= purple_media_get_codecs(media
, "sipe-voice");
388 GList
*local_candidates
= purple_media_get_local_candidates(media
, "sipe-voice", call
->dialog
->with
);
390 // TODO: more sophisticated
391 guint16 local_port
= purple_media_candidate_get_port(local_candidates
->data
);
392 const char *ip
= sipe_utils_get_suitable_local_ip(-1);
394 gchar
*sdp_codecs
= sipe_media_sdp_codecs_format(local_codecs
);
395 gchar
*sdp_codec_ids
= sipe_media_sdp_codec_ids_format(local_codecs
);
396 gchar
*sdp_candidates
= sipe_media_sdp_candidates_format(local_candidates
, call
, remote_candidate
);
397 gchar
*inactive
= call
->state
== SIPE_CALL_HELD
? "a=inactive\r\n" : "";
399 gchar
*body
= g_strdup_printf(
401 "o=- 0 0 IN IP4 %s\r\n"
406 "m=audio %d RTP/AVP%s\r\n"
410 "a=encryption:rejected\r\n"
411 ,ip
, ip
, local_port
, sdp_codec_ids
, sdp_candidates
, inactive
, sdp_codecs
);
414 g_free(sdp_codec_ids
);
415 g_free(sdp_candidates
);
421 sipe_media_session_ready_cb(sipe_media_call
*call
)
423 PurpleMedia
*media
= call
->media
;
424 PurpleAccount
*account
= purple_media_get_account(media
);
426 if (!purple_media_candidates_prepared(media
, NULL
, NULL
))
429 if (!call
->sdp_response
)
430 call
->sdp_response
= sipe_media_create_sdp(call
, FALSE
);
432 if (!purple_media_accepted(media
, NULL
, NULL
)) {
433 if (!call
->legacy_mode
)
434 send_sip_response(account
->gc
, call
->invitation
, 183, "Session Progress", call
->sdp_response
);
436 send_sip_response(account
->gc
, call
->invitation
, 200, "OK", call
->sdp_response
);
437 call
->state
= SIPE_CALL_RUNNING
;
442 sipe_invite_call(struct sipe_account_data
*sip
)
447 sipe_media_call
*call
= sip
->media_call
;
448 struct sip_dialog
*dialog
= call
->dialog
;
450 contact
= get_contact(sip
);
451 hdr
= g_strdup_printf(
452 "Supported: ms-sender\r\n"
453 "ms-keep-alive: UAC;hop-hop=yes\r\n"
455 "Supported: Replaces\r\n"
456 "Content-Type: application/sdp\r\n",
458 call
->state
== SIPE_CALL_HELD
? ";+sip.rendering=\"no\"" : "");
461 body
= sipe_media_create_sdp(call
, TRUE
);
463 send_sip_request(sip
->gc
, "INVITE", dialog
->with
, dialog
->with
, hdr
, body
,
471 notify_state_change(struct sipe_account_data
*sip
, gboolean local
) {
473 sipe_invite_call(sip
);
475 gchar
* body
= sipe_media_create_sdp(sip
->media_call
, TRUE
);
476 send_sip_response(sip
->gc
, sip
->media_call
->invitation
, 200, "OK", body
);
482 sipe_media_stream_info_cb(PurpleMedia
*media
,
483 PurpleMediaInfoType type
,
484 SIPE_UNUSED_PARAMETER gchar
*sid
,
485 SIPE_UNUSED_PARAMETER gchar
*name
,
486 gboolean local
, struct sipe_account_data
*sip
)
488 sipe_media_call
*call
= sip
->media_call
;
490 if (type
== PURPLE_MEDIA_INFO_ACCEPT
)
491 sipe_media_session_ready_cb(call
);
492 else if (type
== PURPLE_MEDIA_INFO_REJECT
) {
493 PurpleAccount
*account
= purple_media_get_account(media
);
494 send_sip_response(account
->gc
, call
->invitation
, 603, "Decline", NULL
);
495 sipe_media_call_free(call
);
496 sip
->media_call
= NULL
;
497 } else if (type
== PURPLE_MEDIA_INFO_HOLD
) {
498 if (call
->state
== SIPE_CALL_HELD
)
501 call
->state
= SIPE_CALL_HELD
;
502 notify_state_change(sip
, local
);
503 purple_media_stream_info(media
, PURPLE_MEDIA_INFO_HOLD
, NULL
, NULL
, TRUE
);
505 } else if (type
== PURPLE_MEDIA_INFO_UNHOLD
) {
506 if (call
->state
== SIPE_CALL_RUNNING
)
509 call
->state
= SIPE_CALL_RUNNING
;
510 notify_state_change(sip
, local
);
512 purple_media_stream_info(media
, PURPLE_MEDIA_INFO_UNHOLD
, NULL
, NULL
, TRUE
);
513 } else if (type
== PURPLE_MEDIA_INFO_HANGUP
) {
514 call
->state
= SIPE_CALL_FINISHED
;
516 send_sip_request(sip
->gc
, "BYE", call
->dialog
->with
, call
->dialog
->with
,
517 NULL
, NULL
, call
->dialog
, NULL
);
518 sipe_media_call_free(call
);
519 sip
->media_call
= NULL
;
524 sipe_media_parse_sdp_frame(sipe_media_call
* call
, gchar
*frame
) {
525 gchar
**lines
= g_strsplit(frame
, "\r\n", 0);
526 GSList
*sdp_attrs
= NULL
;
527 gchar
*remote_ip
= NULL
;
528 guint16 remote_port
= 0;
530 gboolean no_error
= TRUE
;
532 for (ptr
= lines
; *ptr
!= NULL
; ++ptr
) {
533 if (g_str_has_prefix(*ptr
, "a=")) {
534 gchar
**parts
= g_strsplit(*ptr
+ 2, ":", 2);
537 sipe_utils_nameval_free(sdp_attrs
);
542 sdp_attrs
= sipe_utils_nameval_add(sdp_attrs
, parts
[0], parts
[1]);
545 } else if (g_str_has_prefix(*ptr
, "o=")) {
546 gchar
**parts
= g_strsplit(*ptr
+ 2, " ", 6);
547 remote_ip
= g_strdup(parts
[5]);
549 } else if (g_str_has_prefix(*ptr
, "m=")) {
550 gchar
**parts
= g_strsplit(*ptr
+ 2, " ", 3);
551 remote_port
= atoi(parts
[1]);
559 sipe_utils_nameval_free(call
->sdp_attrs
);
560 call
->sdp_attrs
= sdp_attrs
;
561 call
->remote_ip
= remote_ip
;
562 call
->remote_port
= remote_port
;
568 static struct sip_dialog
*
569 sipe_media_dialog_init(struct sip_session
* session
, struct sipmsg
*msg
)
571 gchar
*newTag
= gentag();
572 const gchar
*oldHeader
;
574 struct sip_dialog
*dialog
;
576 oldHeader
= sipmsg_find_header(msg
, "To");
577 newHeader
= g_strdup_printf("%s;tag=%s", oldHeader
, newTag
);
578 sipmsg_remove_header_now(msg
, "To");
579 sipmsg_add_header_now(msg
, "To", newHeader
);
582 dialog
= sipe_dialog_add(session
);
583 dialog
->callid
= g_strdup(session
->callid
);
584 dialog
->with
= parse_from(sipmsg_find_header(msg
, "From"));
585 sipe_dialog_parse(dialog
, msg
, FALSE
);
590 static sipe_media_call
*
591 sipe_media_call_init(struct sipmsg
*msg
)
593 sipe_media_call
*call
;
595 call
= g_new0(sipe_media_call
, 1);
597 if (sipe_media_parse_sdp_frame(call
, msg
->body
) == FALSE
) {
602 call
->invitation
= msg
;
603 call
->legacy_mode
= FALSE
;
604 call
->state
= SIPE_CALL_CONNECTING
;
605 call
->remote_candidates
= sipe_media_parse_remote_candidates(call
);
609 void sipe_media_hold(struct sipe_account_data
*sip
) {
610 if (sip
->media_call
) {
611 purple_media_stream_info(sip
->media_call
->media
, PURPLE_MEDIA_INFO_HOLD
,
616 void sipe_media_unhold(struct sipe_account_data
*sip
) {
617 if (sip
->media_call
) {
618 purple_media_stream_info(sip
->media_call
->media
, PURPLE_MEDIA_INFO_UNHOLD
,
623 void sipe_media_incoming_invite(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
625 PurpleMediaManager
*manager
= purple_media_manager_get();
628 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
630 sipe_media_call
*call
;
631 struct sip_session
*session
;
632 struct sip_dialog
*dialog
;
636 if (sip
->media_call
) {
637 if (sipe_strequal(sip
->media_call
->dialog
->callid
, callid
)) {
640 call
= sip
->media_call
;
642 sipmsg_free(call
->invitation
);
643 msg
->dont_free
= TRUE
;
644 call
->invitation
= msg
;
646 sipmsg_add_header(msg
, "Supported", "Replaces");
648 sipe_utils_nameval_free(call
->sdp_attrs
);
649 call
->sdp_attrs
= NULL
;
650 if (!sipe_media_parse_sdp_frame(call
, msg
->body
)) {
651 // TODO: handle error
654 if (call
->legacy_mode
&& call
->state
== SIPE_CALL_RUNNING
) {
655 sipe_media_hold(sip
);
659 if (sipe_utils_nameval_find(call
->sdp_attrs
, "inactive")) {
660 sipe_media_hold(sip
);
664 if (call
->state
== SIPE_CALL_HELD
) {
665 sipe_media_unhold(sip
);
669 call
->remote_codecs
= sipe_media_parse_remote_codecs(call
);
670 call
->remote_codecs
= sipe_media_prune_remote_codecs(call
->media
, call
->remote_codecs
);
671 if (!call
->remote_codecs
) {
672 // TODO: error no remote codecs
674 if (sipe_backend_set_remote_codecs(call
, call
->dialog
->with
) == FALSE
)
675 printf("ERROR SET REMOTE CODECS"); // TODO
677 rsp
= sipe_media_create_sdp(sip
->media_call
, TRUE
);
678 send_sip_response(sip
->gc
, msg
, 200, "OK", rsp
);
681 // TODO: send Busy Here
682 printf("MEDIA SESSION ALREADY IN PROGRESS");
687 call
= sipe_media_call_init(msg
);
689 session
= sipe_session_find_or_add_chat_by_callid(sip
, callid
);
690 dialog
= sipe_media_dialog_init(session
, msg
);
692 media
= purple_media_manager_create_media(manager
, sip
->account
,
693 "fsrtpconference", dialog
->with
, FALSE
);
695 g_signal_connect(G_OBJECT(media
), "stream-info",
696 G_CALLBACK(sipe_media_stream_info_cb
), sip
);
697 g_signal_connect_swapped(G_OBJECT(media
), "candidates-prepared",
698 G_CALLBACK(sipe_media_session_ready_cb
), call
);
701 call
->session
= session
;
702 call
->dialog
= dialog
;
705 if (call
->legacy_mode
) {
706 purple_media_add_stream(media
, "sipe-voice", dialog
->with
,
707 PURPLE_MEDIA_AUDIO
, FALSE
, "rawudp", 0, NULL
);
709 params
= g_new0(GParameter
, 2);
710 params
[0].name
= "controlling-mode";
711 g_value_init(¶ms
[0].value
, G_TYPE_BOOLEAN
);
712 g_value_set_boolean(¶ms
[0].value
, FALSE
);
713 params
[1].name
= "compatibility-mode";
714 g_value_init(¶ms
[1].value
, G_TYPE_UINT
);
715 g_value_set_uint(¶ms
[1].value
, NICE_COMPATIBILITY_OC2007R2
);
718 purple_media_add_stream(media
, "sipe-voice", dialog
->with
,
719 PURPLE_MEDIA_AUDIO
, FALSE
, "nice", 2, params
);
722 purple_media_add_remote_candidates(media
, "sipe-voice", dialog
->with
,
723 call
->remote_candidates
);
725 call
->remote_codecs
= sipe_media_parse_remote_codecs(call
);
726 call
->remote_codecs
= sipe_media_prune_remote_codecs(call
, call
->remote_codecs
);
727 if (!call
->remote_candidates
|| !call
->remote_codecs
) {
728 sipe_media_call_free(call
);
729 sip
->media_call
= NULL
;
730 printf("ERROR NO CANDIDATES OR CODECS");
733 if (sipe_backend_set_remote_codecs(call
, dialog
->with
) == FALSE
)
734 printf("ERROR SET REMOTE CODECS"); // TODO
736 sip
->media_call
= call
;
738 // TODO: copy message instead of this don't free thing
739 msg
->dont_free
= TRUE
;
740 send_sip_response(sip
->gc
, msg
, 180, "Ringing", NULL
);
743 void sipe_media_hangup(struct sipe_account_data
*sip
)
745 if (sip
->media_call
) {
746 purple_media_stream_info(sip
->media_call
->media
, PURPLE_MEDIA_INFO_HANGUP
,