6 * Copyright (C) 2010-11 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2009 pier11 <pier11@operamail.com>
9 * Implements Remote Call Control (RCC) feature for
10 * integration with legacy enterprise PBX (wired telephony) systems.
11 * Should be applicable to 2005 and 2007(R2) systems.
12 * Inderlying XML protocol CSTA is defined in ECMA-323.
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #include "sipe-common.h"
39 #include "sip-transport.h"
40 #include "sipe-backend.h"
41 #include "sipe-core.h"
42 #include "sipe-core-private.h"
43 #include "sipe-dialog.h"
44 #include "sipe-schedule.h"
45 #include "sipe-utils.h"
48 #define ORIGINATED_CSTA_STATUS "originated"
49 #define DELIVERED_CSTA_STATUS "delivered"
50 #define ESTABLISHED_CSTA_STATUS "established"
53 * Data model for interaction with SIP/CSTA Gateway
57 /** SIP/CSTA Gateway's SIP URI */
59 /** dialog with SIP/CSTA Gateway */
60 struct sip_dialog
*dialog
;
62 gchar
*gateway_status
;
63 gchar
*monitor_cross_ref_id
;
66 /** destination tel: URI */
69 /* our device ID as reported by SIP/CSTA gateway */
74 * Sends CSTA RequestSystemStatus request to SIP/CSTA Gateway.
75 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
77 #define SIP_SEND_CSTA_REQUEST_SYSTEM_STATUS \
78 "<?xml version=\"1.0\"?>"\
79 "<RequestSystemStatus xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
83 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
87 "</RequestSystemStatus>"
90 * Sends CSTA GetCSTAFeatures request to SIP/CSTA Gateway.
91 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
93 #define SIP_SEND_CSTA_GET_CSTA_FEATURES \
94 "<?xml version=\"1.0\"?>"\
95 "<GetCSTAFeatures xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
99 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
106 * Sends CSTA start monitor request to SIP/CSTA Gateway.
107 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
109 #define SIP_SEND_CSTA_MONITOR_START \
110 "<?xml version=\"1.0\"?>"\
111 "<MonitorStart xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
113 "<deviceObject>%s</deviceObject>"\
118 * Sends CSTA stop monitor request to SIP/CSTA Gateway.
119 * @param monitor_cross_ref_id (%s) Ex.: 99fda87c
121 #define SIP_SEND_CSTA_MONITOR_STOP \
122 "<?xml version=\"1.0\"?>"\
123 "<MonitorStop xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
124 "<monitorCrossRefID>%s</monitorCrossRefID>"\
128 * Sends CSTA make call request to SIP/CSTA Gateway.
129 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
130 * @param to_tel_uri (%s) Ex.: tel:+3222220220
132 #define SIP_SEND_CSTA_MAKE_CALL \
133 "<?xml version=\"1.0\"?>"\
134 "<MakeCall xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
135 "<callingDevice>%s</callingDevice>"\
136 "<calledDirectoryNumber>%s</calledDirectoryNumber>"\
137 "<autoOriginate>doNotPrompt</autoOriginate>"\
141 * Sends CSTA ClearConnection request to SIP/CSTA Gateway.
142 * @param call_id (%s) Ex.: 0_99f261b4
143 * @param device_id (%s) Same as in OriginatedEvent, DeliveredEvent notifications.
144 * Ex.: tel:73124;phone-context=dialstring
146 #define SIP_SEND_CSTA_CLEAR_CONNECTION \
147 "<?xml version=\"1.0\"?>"\
148 "<ClearConnection xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
149 "<connectionToBeCleared>"\
150 "<callID>%s</callID>"\
151 "<deviceID>%s</deviceID>"\
152 "</connectionToBeCleared>"\
157 sip_to_tel_uri0(const gchar
*phone
)
159 if (!phone
|| strlen(phone
) == 0) return NULL
;
161 if (g_str_has_prefix(phone
, "tel:")) {
162 return g_strdup(phone
);
164 gchar
*tel_uri
= g_malloc(strlen(phone
) + 4 + 1);
165 gchar
*dest_p
= g_stpcpy(tel_uri
, "tel:");
166 for (; *phone
; phone
++) {
167 if (*phone
== ' ') continue;
168 if (*phone
== '(') continue;
169 if (*phone
== ')') continue;
170 if (*phone
== '-') continue;
171 if (*phone
== '.') continue;
180 sip_to_tel_uri(const gchar
*phone
)
182 gchar
*res
= sip_to_tel_uri0(phone
);
184 /* strips everything starting with 'v:' if any */
185 if (res
&& (v
= strstr(res
, "v:"))) {
188 res
= g_strndup(res
, v
- res
);
196 sip_tel_uri_denormalize(const gchar
*tel_uri
)
198 if (!tel_uri
) return NULL
;
200 if (g_str_has_prefix(tel_uri
, "tel:")) {
201 return g_strdup(tel_uri
+ 4);
203 return g_strdup(tel_uri
);
208 sip_csta_initialize(struct sipe_core_private
*sipe_private
,
209 const gchar
*line_uri
,
212 if(!sipe_private
->csta
) {
213 sipe_private
->csta
= g_new0(struct sip_csta
, 1);
214 sipe_private
->csta
->line_uri
= g_strdup(line_uri
);
215 sipe_private
->csta
->gateway_uri
= g_strdup(server
);
217 SIPE_DEBUG_INFO_NOFORMAT("sip_csta_initialize: sipe_private->csta is already instantiated, exiting.");
221 /** get CSTA feautures's callback */
223 process_csta_get_features_response(SIPE_UNUSED_PARAMETER
struct sipe_core_private
*sipe_private
,
225 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
227 if (msg
->response
>= 400) {
228 SIPE_DEBUG_INFO_NOFORMAT("process_csta_get_features_response: Get CSTA features response is not 200. Failed to get features.");
229 /* @TODO notify user of failure to get CSTA features */
232 else if (msg
->response
== 200) {
233 SIPE_DEBUG_INFO("process_csta_get_features_response:\n%s", msg
->body
? msg
->body
: "");
239 /** get CSTA feautures */
241 sip_csta_get_features(struct sipe_core_private
*sipe_private
)
246 if (!sipe_private
->csta
|| !sipe_private
->csta
->dialog
|| !sipe_private
->csta
->dialog
->is_established
) {
247 SIPE_DEBUG_INFO_NOFORMAT("sip_csta_get_features: no dialog with CSTA, exiting.");
252 "Content-Disposition: signal;handling=required\r\n"
253 "Content-Type: application/csta+xml\r\n");
255 body
= g_strdup_printf(
256 SIP_SEND_CSTA_GET_CSTA_FEATURES
,
257 sipe_private
->csta
->line_uri
);
259 sip_transport_info(sipe_private
,
262 sipe_private
->csta
->dialog
,
263 process_csta_get_features_response
);
268 /** Monitor Start's callback */
270 process_csta_monitor_start_response(struct sipe_core_private
*sipe_private
,
272 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
274 SIPE_DEBUG_INFO("process_csta_monitor_start_response:\n%s", msg
->body
? msg
->body
: "");
276 if (!sipe_private
->csta
) {
277 SIPE_DEBUG_INFO_NOFORMAT("process_csta_monitor_start_response: sipe_private->csta is not initializzed, exiting");
281 if (msg
->response
>= 400) {
282 SIPE_DEBUG_INFO_NOFORMAT("process_csta_monitor_start_response: Monitor Start response is not 200. Failed to start monitor.");
283 /* @TODO notify user of failure to start monitor */
286 else if (msg
->response
== 200) {
287 sipe_xml
*xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
288 g_free(sipe_private
->csta
->monitor_cross_ref_id
);
289 sipe_private
->csta
->monitor_cross_ref_id
= sipe_xml_data(sipe_xml_child(xml
, "monitorCrossRefID"));
290 SIPE_DEBUG_INFO("process_csta_monitor_start_response: monitor_cross_ref_id=%s",
291 sipe_private
->csta
->monitor_cross_ref_id
? sipe_private
->csta
->monitor_cross_ref_id
: "");
300 sip_csta_monitor_start(struct sipe_core_private
*sipe_private
)
305 if (!sipe_private
->csta
|| !sipe_private
->csta
->dialog
|| !sipe_private
->csta
->dialog
->is_established
) {
306 SIPE_DEBUG_INFO_NOFORMAT("sip_csta_monitor_start: no dialog with CSTA, exiting.");
311 "Content-Disposition: signal;handling=required\r\n"
312 "Content-Type: application/csta+xml\r\n");
314 body
= g_strdup_printf(
315 SIP_SEND_CSTA_MONITOR_START
,
316 sipe_private
->csta
->line_uri
);
318 sip_transport_info(sipe_private
,
321 sipe_private
->csta
->dialog
,
322 process_csta_monitor_start_response
);
329 sip_csta_monitor_stop(struct sipe_core_private
*sipe_private
)
334 if (!sipe_private
->csta
|| !sipe_private
->csta
->dialog
|| !sipe_private
->csta
->dialog
->is_established
) {
335 SIPE_DEBUG_INFO_NOFORMAT("sip_csta_monitor_stop: no dialog with CSTA, exiting.");
339 if (!sipe_private
->csta
->monitor_cross_ref_id
) {
340 SIPE_DEBUG_INFO_NOFORMAT("sip_csta_monitor_stop: no monitor_cross_ref_id, exiting.");
345 "Content-Disposition: signal;handling=required\r\n"
346 "Content-Type: application/csta+xml\r\n");
348 body
= g_strdup_printf(
349 SIP_SEND_CSTA_MONITOR_STOP
,
350 sipe_private
->csta
->monitor_cross_ref_id
);
352 sip_transport_info(sipe_private
,
355 sipe_private
->csta
->dialog
,
362 sipe_invite_csta_gateway(struct sipe_core_private
*sipe_private
,
367 process_invite_csta_gateway_response(struct sipe_core_private
*sipe_private
,
369 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
371 SIPE_DEBUG_INFO("process_invite_csta_gateway_response:\n%s", msg
->body
? msg
->body
: "");
373 if (!sipe_private
->csta
) {
374 SIPE_DEBUG_INFO_NOFORMAT("process_invite_csta_gateway_response: sipe_private->csta is not initializzed, exiting");
378 if (!sipe_private
->csta
->dialog
) {
379 SIPE_DEBUG_INFO_NOFORMAT("process_invite_csta_gateway_response: GSTA dialog is NULL, exiting");
383 sipe_dialog_parse(sipe_private
->csta
->dialog
, msg
, TRUE
);
385 if (msg
->response
>= 200) {
386 /* send ACK to CSTA */
387 sipe_private
->csta
->dialog
->cseq
= 0;
388 sip_transport_ack(sipe_private
, sipe_private
->csta
->dialog
);
389 sipe_private
->csta
->dialog
->outgoing_invite
= NULL
;
390 sipe_private
->csta
->dialog
->is_established
= TRUE
;
393 if (msg
->response
>= 400) {
394 SIPE_DEBUG_INFO_NOFORMAT("process_invite_csta_gateway_response: INVITE response is not 200. Failed to join CSTA.");
395 /* @TODO notify user of failure to join CSTA */
398 else if (msg
->response
== 200) {
399 sipe_xml
*xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
401 g_free(sipe_private
->csta
->gateway_status
);
402 sipe_private
->csta
->gateway_status
= sipe_xml_data(sipe_xml_child(xml
, "systemStatus"));
403 SIPE_DEBUG_INFO("process_invite_csta_gateway_response: gateway_status=%s",
404 sipe_private
->csta
->gateway_status
? sipe_private
->csta
->gateway_status
: "");
405 if (sipe_strcase_equal(sipe_private
->csta
->gateway_status
, "normal")) {
406 if (!sipe_private
->csta
->monitor_cross_ref_id
) {
407 sip_csta_get_features(sipe_private
);
408 sip_csta_monitor_start(sipe_private
);
411 SIPE_DEBUG_INFO("process_invite_csta_gateway_response: ERROR: CSTA status is %s, won't continue.",
412 sipe_private
->csta
->gateway_status
);
413 /* @TODO notify user of failure to join CSTA */
417 /* schedule re-invite. RFC4028 */
418 if (sipe_private
->csta
->dialog
->expires
) {
419 sipe_schedule_seconds(sipe_private
,
422 sipe_private
->csta
->dialog
->expires
- 60, /* 1 minute earlier */
423 sipe_invite_csta_gateway
,
431 /** Creates long living dialog with SIP/CSTA Gateway */
432 /* should be re-entrant as require to sent re-invites every 10 min to refresh */
434 sipe_invite_csta_gateway(struct sipe_core_private
*sipe_private
,
435 SIPE_UNUSED_PARAMETER gpointer unused
)
441 if (!sipe_private
->csta
) {
442 SIPE_DEBUG_INFO_NOFORMAT("sipe_invite_csta_gateway: sipe_private->csta is uninitialized, exiting");
446 if(!sipe_private
->csta
->dialog
) {
447 sipe_private
->csta
->dialog
= g_new0(struct sip_dialog
, 1);
448 sipe_private
->csta
->dialog
->callid
= gencallid();
449 sipe_private
->csta
->dialog
->with
= g_strdup(sipe_private
->csta
->gateway_uri
);
451 if (!(sipe_private
->csta
->dialog
->ourtag
)) {
452 sipe_private
->csta
->dialog
->ourtag
= gentag();
455 contact
= get_contact(sipe_private
);
456 hdr
= g_strdup_printf(
458 "Supported: timer\r\n"
459 "Content-Disposition: signal;handling=required\r\n"
460 "Content-Type: application/csta+xml\r\n",
464 body
= g_strdup_printf(
465 SIP_SEND_CSTA_REQUEST_SYSTEM_STATUS
,
466 sipe_private
->csta
->line_uri
);
468 sipe_private
->csta
->dialog
->outgoing_invite
=
469 sip_transport_invite(sipe_private
,
472 sipe_private
->csta
->dialog
,
473 process_invite_csta_gateway_response
);
479 sip_csta_open(struct sipe_core_private
*sipe_private
,
480 const gchar
*line_uri
,
483 sip_csta_initialize(sipe_private
, line_uri
, server
);
484 sipe_invite_csta_gateway(sipe_private
, NULL
);
488 sip_csta_free(struct sip_csta
*csta
)
492 g_free(csta
->line_uri
);
493 g_free(csta
->gateway_uri
);
495 sipe_dialog_free(csta
->dialog
);
497 g_free(csta
->gateway_status
);
498 g_free(csta
->monitor_cross_ref_id
);
499 g_free(csta
->line_status
);
500 g_free(csta
->to_tel_uri
);
501 g_free(csta
->call_id
);
502 g_free(csta
->device_id
);
508 sip_csta_close(struct sipe_core_private
*sipe_private
)
510 if (sipe_private
->csta
) {
511 sip_csta_monitor_stop(sipe_private
);
514 if (sipe_private
->csta
&& sipe_private
->csta
->dialog
) {
515 /* send BYE to CSTA */
516 sip_transport_bye(sipe_private
, sipe_private
->csta
->dialog
);
519 sip_csta_free(sipe_private
->csta
);
524 /** Make Call's callback */
526 process_csta_make_call_response(struct sipe_core_private
*sipe_private
,
528 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
530 SIPE_DEBUG_INFO("process_csta_make_call_response:\n%s", msg
->body
? msg
->body
: "");
532 if (!sipe_private
->csta
) {
533 SIPE_DEBUG_INFO_NOFORMAT("process_csta_make_call_response: sipe_private->csta is not initializzed, exiting");
537 if (msg
->response
>= 400) {
538 SIPE_DEBUG_INFO_NOFORMAT("process_csta_make_call_response: Make Call response is not 200. Failed to make call.");
539 /* @TODO notify user of failure to make call */
542 else if (msg
->response
== 200) {
544 const sipe_xml
*xn_calling_device
;
547 SIPE_DEBUG_INFO_NOFORMAT("process_csta_make_call_response: SUCCESS");
549 xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
550 xn_calling_device
= sipe_xml_child(xml
, "callingDevice");
551 device_id
= sipe_xml_data(sipe_xml_child(xn_calling_device
, "deviceID"));
552 if (sipe_strequal(sipe_private
->csta
->line_uri
, device_id
)) {
553 g_free(sipe_private
->csta
->call_id
);
554 sipe_private
->csta
->call_id
= sipe_xml_data(sipe_xml_child(xn_calling_device
, "callID"));
555 SIPE_DEBUG_INFO("process_csta_make_call_response: call_id=%s", sipe_private
->csta
->call_id
? sipe_private
->csta
->call_id
: "");
565 static void sip_csta_make_call(struct sipe_core_private
*sipe_private
,
566 const gchar
* to_tel_uri
)
572 SIPE_DEBUG_INFO_NOFORMAT("sip_csta_make_call: no tel URI parameter provided, exiting.");
576 if (!sipe_private
->csta
|| !sipe_private
->csta
->dialog
|| !sipe_private
->csta
->dialog
->is_established
) {
577 SIPE_DEBUG_INFO_NOFORMAT("sip_csta_make_call: no dialog with CSTA, exiting.");
581 g_free(sipe_private
->csta
->to_tel_uri
);
582 sipe_private
->csta
->to_tel_uri
= g_strdup(to_tel_uri
);
585 "Content-Disposition: signal;handling=required\r\n"
586 "Content-Type: application/csta+xml\r\n");
588 body
= g_strdup_printf(
589 SIP_SEND_CSTA_MAKE_CALL
,
590 sipe_private
->csta
->line_uri
,
591 sipe_private
->csta
->to_tel_uri
);
593 sip_transport_info(sipe_private
,
596 sipe_private
->csta
->dialog
,
597 process_csta_make_call_response
);
603 sip_csta_update_id_and_status(struct sip_csta
*csta
,
604 const sipe_xml
*node
,
607 gchar
*call_id
= sipe_xml_data(sipe_xml_child(node
, "callID"));
609 if (!sipe_strequal(call_id
, csta
->call_id
)) {
610 SIPE_DEBUG_INFO("sipe_csta_update_id_and_status: callID (%s) does not match", call_id
);
614 /* free old line status */
615 g_free(csta
->line_status
);
616 csta
->line_status
= NULL
;
621 gchar
*device_id
= sipe_xml_data(sipe_xml_child(node
, "deviceID"));
622 SIPE_DEBUG_INFO("sipe_csta_update_id_and_status: device_id=(%s)", device_id
? device_id
: "");
624 g_free(csta
->device_id
);
625 csta
->device_id
= device_id
;
628 /* set new line status */
629 csta
->line_status
= g_strdup(status
);
633 /* clean up cleared connection */
634 g_free(csta
->to_tel_uri
);
635 csta
->to_tel_uri
= NULL
;
636 g_free(csta
->call_id
);
637 csta
->call_id
= NULL
;
638 g_free(csta
->device_id
);
639 csta
->device_id
= NULL
;
647 process_incoming_info_csta(struct sipe_core_private
*sipe_private
,
650 gchar
*monitor_cross_ref_id
;
651 sipe_xml
*xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
655 monitor_cross_ref_id
= sipe_xml_data(sipe_xml_child(xml
, "monitorCrossRefID"));
657 if(!sipe_private
->csta
|| !sipe_strequal(monitor_cross_ref_id
, sipe_private
->csta
->monitor_cross_ref_id
))
659 SIPE_DEBUG_INFO("process_incoming_info_csta: monitorCrossRefID (%s) does not match, exiting",
660 monitor_cross_ref_id
? monitor_cross_ref_id
: "");
664 if (sipe_strequal(sipe_xml_name(xml
), "OriginatedEvent"))
666 sip_csta_update_id_and_status(sipe_private
->csta
,
667 sipe_xml_child(xml
, "originatedConnection"),
668 ORIGINATED_CSTA_STATUS
);
670 else if (sipe_strequal(sipe_xml_name(xml
), "DeliveredEvent"))
672 sip_csta_update_id_and_status(sipe_private
->csta
,
673 sipe_xml_child(xml
, "connection"),
674 DELIVERED_CSTA_STATUS
);
676 else if (sipe_strequal(sipe_xml_name(xml
), "EstablishedEvent"))
678 sip_csta_update_id_and_status(sipe_private
->csta
,
679 sipe_xml_child(xml
, "establishedConnection"),
680 ESTABLISHED_CSTA_STATUS
);
682 else if (sipe_strequal(sipe_xml_name(xml
), "ConnectionClearedEvent"))
684 sip_csta_update_id_and_status(sipe_private
->csta
,
685 sipe_xml_child(xml
, "droppedConnection"),
690 g_free(monitor_cross_ref_id
);
694 gboolean
sip_csta_is_idle(struct sipe_core_private
*sipe_private
)
696 return(sipe_private
->csta
&& !sipe_private
->csta
->line_status
);
699 void sipe_core_buddy_make_call(struct sipe_core_public
*sipe_public
,
703 gchar
*tel_uri
= sip_to_tel_uri(phone
);
705 SIPE_DEBUG_INFO("sipe_core_buddy_make_call: calling number: %s",
706 tel_uri
? tel_uri
: "");
707 sip_csta_make_call(SIPE_CORE_PRIVATE
, tel_uri
);