summaryrefslogtreecommitdiffstats
path: root/tinySIP/src/dialogs/tsip_dialog_invite.server.c
diff options
context:
space:
mode:
Diffstat (limited to 'tinySIP/src/dialogs/tsip_dialog_invite.server.c')
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_invite.server.c790
1 files changed, 790 insertions, 0 deletions
diff --git a/tinySIP/src/dialogs/tsip_dialog_invite.server.c b/tinySIP/src/dialogs/tsip_dialog_invite.server.c
new file mode 100644
index 0000000..2a7a37c
--- /dev/null
+++ b/tinySIP/src/dialogs/tsip_dialog_invite.server.c
@@ -0,0 +1,790 @@
+/*
+* Copyright (C) 2010-2011 Mamadou Diop.
+*
+* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]org>
+*
+* This file is part of Open Source Doubango Framework.
+*
+* DOUBANGO is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as publishd by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* DOUBANGO is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with DOUBANGO.
+*
+*/
+
+/**@file tsip_dialog_invite.client.c
+ * @brief SIP dialog INVITE as per RFC 3261.
+ * The SOA machine is designed as per RFC 3264 and draft-ietf-sipping-sip-offeranswer-12.
+ * MMTel services implementation follow 3GPP TS 24.173.
+ *
+ * @author Mamadou Diop <diopmamadou(at)doubango[dot]org>
+ *
+
+ */
+#include "tinysip/dialogs/tsip_dialog_invite.h"
+
+#include "tinysip/dialogs/tsip_dialog_invite.common.h"
+
+#include "tinysip/transports/tsip_transport_layer.h"
+
+#include "tinysip/headers/tsip_header_Dummy.h"
+#include "tinysip/headers/tsip_header_Min_SE.h"
+#include "tinysip/headers/tsip_header_RAck.h"
+#include "tinysip/headers/tsip_header_Require.h"
+#include "tinysip/headers/tsip_header_Session_Expires.h"
+
+#include "tsk_debug.h"
+
+static const char* supported_options[] = { "100rel", "precondition", "timer" };
+
+/* ======================== external functions ======================== */
+extern int tsip_dialog_invite_msession_start(tsip_dialog_invite_t *self);
+extern int send_RESPONSE(tsip_dialog_invite_t *self, const tsip_request_t* request, short code, const char* phrase, tsk_bool_t force_sdp);
+extern int tsip_dialog_invite_process_ro(tsip_dialog_invite_t *self, const tsip_message_t* message);
+extern int tsip_dialog_invite_stimers_schedule(tsip_dialog_invite_t* self, uint64_t timeout);
+extern int send_ERROR(tsip_dialog_invite_t* self, const tsip_request_t* request, short code, const char* phrase, const char* reason);
+
+extern int tsip_dialog_invite_timer_callback(const tsip_dialog_invite_t* self, tsk_timer_id_t timer_id);
+extern tsk_bool_t tsip_dialog_invite_ice_is_enabled(const tsip_dialog_invite_t * self);
+extern tsk_bool_t tsip_dialog_invite_ice_is_connected(const tsip_dialog_invite_t * self);
+
+/* ======================== internal functions ======================== */
+static int send_UNSUPPORTED(tsip_dialog_invite_t* self, const tsip_request_t* request, const char* option);
+
+/* ======================== transitions ======================== */
+static int s0000_Started_2_Terminated_X_iINVITE(va_list *app); // Failure
+static int s0000_Started_2_Started_X_iINVITE(va_list *app); // Session Interval Too Small
+static int s0000_Started_2_InProgress_X_iINVITE(va_list *app); // 100rel supported
+static int s0000_Started_2_Ringing_X_iINVITE(va_list *app); // Neither 100rel nor QoS
+static int s0000_InProgress_2_InProgress_X_iPRACK(va_list *app); // PRACK for our 18x response (with QoS)
+static int s0000_InProgress_2_Ringing_X_iPRACK(va_list *app); // PRACK for our 18x response (without QoS)
+static int s0000_InProgress_2_InProgress_X_iUPDATE(va_list *app); // QoS cannot resume
+static int s0000_InProgress_2_Ringing_X_iUPDATE(va_list *app); // QoS can resume (do not alert user, wait for PRACK)
+static int s0000_Inprogress_2_Terminated_X_iCANCEL(va_list *app);
+static int s0000_Ringing_2_Ringing_X_iPRACK(va_list *app); // Alert user
+static int s0000_Ringing_2_Connected_X_Accept(va_list *app);
+static int s0000_Ringing_2_Terminated_X_Reject(va_list *app);
+static int s0000_Ringing_2_Terminated_X_iCANCEL(va_list *app);
+static int s0000_Any_2_Any_X_timer100rel(va_list *app);
+
+/* ======================== conds ======================== */
+static tsk_bool_t _fsm_cond_bad_extension(tsip_dialog_invite_t* self, tsip_message_t* message)
+{
+ const tsip_header_Require_t* requireHdr;
+ const tsk_list_item_t* item;
+ tsk_size_t i, j;
+
+ /* Check if we support all extensions */
+ for(i = 0; (requireHdr = (const tsip_header_Require_t*)tsip_message_get_headerAt(message, tsip_htype_Require, i)); i++){
+ tsk_bool_t bad_extension = tsk_false;
+ const tsk_string_t* option = tsk_null;
+ tsk_list_foreach(item, requireHdr->options){
+ option = item->data;
+ bad_extension = tsk_true;
+ for(j = 0; option && j<sizeof(supported_options)/sizeof(const char*); j++){
+ if(tsk_striequals(option->value, supported_options[j])){
+ bad_extension = tsk_false;
+ break;
+ }
+ }
+ if(bad_extension){
+ break;
+ }
+ }
+ if(bad_extension && option){
+ send_UNSUPPORTED(self, message, option->value);
+ return tsk_true;
+ }
+ }
+
+
+ return tsk_false;
+}
+
+static tsk_bool_t _fsm_cond_bad_content(tsip_dialog_invite_t* self, tsip_message_t* message)
+{
+ int ret;
+ const tsdp_message_t* sdp_lo;
+ tsk_bool_t bodiless_INVITE = (TSIP_DIALOG(self)->state == tsip_initial && !TSIP_MESSAGE_HAS_CONTENT(message)); // Initial Bodiless INVITE
+
+ /* Check remote offer */
+ if((ret = tsip_dialog_invite_process_ro(self, message))){
+ ret = send_ERROR(self, message, 488, "Not Acceptable", "SIP; cause=488; text=\"Bad content\"");
+ return tsk_true;
+ }
+ /* generate local offer and check it's validity */
+ if(self->msession_mgr && (sdp_lo = tmedia_session_mgr_get_lo(self->msession_mgr))){
+ /* check that we have at least one valid session (Only if no bodiless initial INVITE) */
+ if(!bodiless_INVITE && !tmedia_session_mgr_has_active_session(self->msession_mgr)){
+ ret = send_ERROR(self, message, 488, "Not Acceptable", "SIP; cause=488; text=\"No common codecs\"");
+ return tsk_true;
+ }
+ // media type could change if there are zombies (medias with port equal to zero)
+ TSIP_DIALOG_GET_SS(self)->media.type = self->msession_mgr->type;
+ }
+ else{
+ ret = send_ERROR(self, message, 488, "Not Acceptable", "SIP; cause=488; text=\"Bad content\"");
+ return tsk_true;
+ }
+
+ return tsk_false;
+}
+
+static tsk_bool_t _fsm_cond_toosmall(tsip_dialog_invite_t* self, tsip_message_t* message)
+{
+ if(TSIP_DIALOG_GET_SS(self)->media.timers.timeout && (tsip_message_supported(message, "timer") || tsip_message_required(message, "timer"))){
+ const tsip_header_Session_Expires_t* Session_Expires;
+ if((Session_Expires = (const tsip_header_Session_Expires_t*)tsip_message_get_header(message, tsip_htype_Session_Expires))){
+ if(Session_Expires->delta_seconds < TSIP_SESSION_EXPIRES_MIN_VALUE){
+ self->stimers.minse = TSIP_SESSION_EXPIRES_MIN_VALUE;
+ send_RESPONSE(self, message, 422, "Session Interval Too Small", tsk_false);
+ return tsk_true;
+ }
+ else{
+ const tsip_header_Min_SE_t* Min_SE;
+ self->stimers.timer.timeout = Session_Expires->delta_seconds;
+ tsk_strupdate(&self->stimers.refresher, Session_Expires->refresher_uas ? "uas" : "uac");
+ self->stimers.is_refresher = tsk_striequals(self->stimers.refresher, "uas");
+ if((Min_SE = (const tsip_header_Min_SE_t*)tsip_message_get_header(message, tsip_htype_Min_SE))){
+ self->stimers.minse = Min_SE->delta_seconds;
+ }
+ }
+ }
+ }
+ return tsk_false;
+}
+
+// 100rel && (QoS or ICE)
+static tsk_bool_t _fsm_cond_use_early_media(tsip_dialog_invite_t* self, tsip_message_t* message)
+{
+ if((tsip_message_supported(message, "100rel") && self->supported._100rel) || tsip_message_required(message, "100rel")){
+ if((tsip_message_supported(message, "precondition") && self->supported.precondition) || tsip_message_required(message, "precondition")){
+ return tsk_true;
+ }
+ }
+#if 0
+ if(tsip_dialog_invite_ice_is_enabled(self)){
+ return tsk_true;
+ }
+#endif
+ return tsk_false;
+}
+
+
+static tsk_bool_t _fsm_cond_prack_match(tsip_dialog_invite_t* self, tsip_message_t* message)
+{
+ const tsip_header_RAck_t* RAck;
+
+ if(!self->last_o1xxrel){
+ return tsk_false;
+ }
+
+ if((RAck = (const tsip_header_RAck_t*)tsip_message_get_header(message, tsip_htype_RAck))){
+ if((RAck->seq == self->rseq) &&
+ (tsk_striequals(RAck->method, self->last_o1xxrel->CSeq->method)) &&
+ (RAck->cseq == self->last_o1xxrel->CSeq->seq)){
+ self->rseq++;
+ return tsk_true;
+ }
+ else{
+ TSK_DEBUG_WARN("Failed to match PRACK request");
+ }
+ }
+
+ return tsk_false;
+}
+static tsk_bool_t _fsm_cond_negociates_preconditions(tsip_dialog_invite_t* self, tsip_message_t* rPRACK)
+{
+ //tsip_message_supported(self->last_iInvite, "precondition") || tsip_message_required(self->last_iInvite, "precondition")
+ if(tsip_message_required(self->last_iInvite, "precondition") || (self->msession_mgr && self->msession_mgr->qos.strength == tmedia_qos_strength_mandatory)){
+ return tsk_true;
+ }
+ return tsk_false;
+}
+static tsk_bool_t _fsm_cond_cannotresume(tsip_dialog_invite_t* self, tsip_message_t* rUPDATE)
+{
+ if(!tsip_dialog_invite_process_ro(self, rUPDATE)){
+ return !tmedia_session_mgr_canresume(self->msession_mgr);
+ }
+ else{
+ return tsk_false;
+ }
+}
+
+static tsk_bool_t _fsm_cond_initial_iack_pending(tsip_dialog_invite_t* self, tsip_message_t* rACK)
+{
+ return self->is_initial_iack_pending;
+}
+
+
+
+/* Init FSM */
+int tsip_dialog_invite_server_init(tsip_dialog_invite_t *self)
+{
+ return tsk_fsm_set(TSIP_DIALOG_GET_FSM(self),
+
+ /*=======================
+ * === Started ===
+ */
+ // Started -> (Bad Extendion) -> Terminated
+ TSK_FSM_ADD(_fsm_state_Started, _fsm_action_iINVITE, _fsm_cond_bad_extension, _fsm_state_Terminated, s0000_Started_2_Terminated_X_iINVITE, "s0000_Started_2_Terminated_X_iINVITE"),
+ // Started -> (Bad content) -> Terminated
+ TSK_FSM_ADD(_fsm_state_Started, _fsm_action_iINVITE, _fsm_cond_bad_content, _fsm_state_Terminated, s0000_Started_2_Terminated_X_iINVITE, "s0000_Started_2_Terminated_X_iINVITE"),
+ // Started -> (Session Interval Too Small) -> Started
+ TSK_FSM_ADD(_fsm_state_Started, _fsm_action_iINVITE, _fsm_cond_toosmall, _fsm_state_Started, s0000_Started_2_Started_X_iINVITE, "s0000_Started_2_Started_X_iINVITE"),
+ // Started -> (100rel && (QoS or ICE)) -> InProgress
+ TSK_FSM_ADD(_fsm_state_Started, _fsm_action_iINVITE, _fsm_cond_use_early_media, _fsm_state_InProgress, s0000_Started_2_InProgress_X_iINVITE, "s0000_Started_2_InProgress_X_iINVITE"),
+ // Started -> (non-100rel and non-QoS, referred to as "basic") -> Ringing
+ TSK_FSM_ADD_ALWAYS(_fsm_state_Started, _fsm_action_iINVITE, _fsm_state_Ringing, s0000_Started_2_Ringing_X_iINVITE, "s0000_Started_2_Ringing_X_iINVITE"),
+
+
+ /*=======================
+ * === InProgress ===
+ */
+ // InProgress ->(iPRACK with QoS) -> InProgress
+ TSK_FSM_ADD(_fsm_state_InProgress, _fsm_action_iPRACK, _fsm_cond_negociates_preconditions, _fsm_state_InProgress, s0000_InProgress_2_InProgress_X_iPRACK, "s0000_InProgress_2_InProgress_X_iPRACK"),
+ // InProgress ->(iPRACK without QoS) -> Ringing
+ TSK_FSM_ADD(_fsm_state_InProgress, _fsm_action_iPRACK, _fsm_cond_prack_match, _fsm_state_Ringing, s0000_InProgress_2_Ringing_X_iPRACK, "s0000_InProgress_2_Ringing_X_iPRACK"),
+ // InProgress ->(iUPDATE but cannot resume) -> InProgress
+ TSK_FSM_ADD(_fsm_state_InProgress, _fsm_action_iUPDATE, _fsm_cond_cannotresume, _fsm_state_InProgress, s0000_InProgress_2_InProgress_X_iUPDATE, "s0000_InProgress_2_InProgress_X_iUPDATE"),
+ // InProgress ->(iUPDATE can resume) -> Ringing
+ TSK_FSM_ADD_ALWAYS(_fsm_state_InProgress, _fsm_action_iUPDATE, _fsm_state_Ringing, s0000_InProgress_2_Ringing_X_iUPDATE, "s0000_InProgress_2_Ringing_X_iUPDATE"),
+ // InProgress ->(iCANCEL) -> Terminated
+ TSK_FSM_ADD_ALWAYS(_fsm_state_InProgress, _fsm_action_iCANCEL, _fsm_state_Terminated, s0000_Inprogress_2_Terminated_X_iCANCEL, "s0000_Inprogress_2_Terminated_X_iCANCEL"),
+
+
+ /*=======================
+ * === Ringing ===
+ */
+ // Ringing -> (iPRACK) -> Ringing
+ TSK_FSM_ADD(_fsm_state_Ringing, _fsm_action_iPRACK, _fsm_cond_prack_match, _fsm_state_Ringing, s0000_Ringing_2_Ringing_X_iPRACK, "s0000_Ringing_2_Ringing_X_iPRACK"),
+ // Ringing -> (oAccept) -> Connected
+ TSK_FSM_ADD_ALWAYS(_fsm_state_Ringing, _fsm_action_accept, _fsm_state_Connected, s0000_Ringing_2_Connected_X_Accept, "s0000_Ringing_2_Connected_X_Accept"),
+ // Ringing -> (oReject) -> Terminated
+ TSK_FSM_ADD_ALWAYS(_fsm_state_Ringing, _fsm_action_reject, _fsm_state_Terminated, s0000_Ringing_2_Terminated_X_Reject, "s0000_Ringing_2_Terminated_X_Reject"),
+ // Ringing ->(iCANCEL) -> Terminated
+ TSK_FSM_ADD_ALWAYS(_fsm_state_Ringing, _fsm_action_iCANCEL, _fsm_state_Terminated, s0000_Ringing_2_Terminated_X_iCANCEL, "s0000_Ringing_2_Terminated_X_iCANCEL"),
+
+ /*=======================
+ * === FRESH CONNECTED ===
+ */
+ // Fresh Connected [ACK is pending] ->(iCANCEL) -> Terminated
+ TSK_FSM_ADD(_fsm_state_Connected, _fsm_action_iCANCEL, _fsm_cond_initial_iack_pending, _fsm_state_Terminated, s0000_Ringing_2_Terminated_X_iCANCEL, "s0000_FreshConnected_2_Terminated_X_iCANCEL"),
+
+ /*=======================
+ * === ANY ===
+ */
+ // Any ->(timer100rel) -> Any
+ TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_timer100rel, tsk_fsm_state_any, s0000_Any_2_Any_X_timer100rel, "s0000_Any_2_Any_X_timer100rel"),
+
+
+ TSK_FSM_ADD_NULL());
+}
+
+//--------------------------------------------------------
+// == STATE MACHINE BEGIN ==
+//--------------------------------------------------------
+
+
+/* Started -> (Failure) -> Terminated */
+int s0000_Started_2_Terminated_X_iINVITE(va_list *app)
+{
+ tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
+ /* tsip_request_t *request = va_arg(*app, tsip_request_t *); */
+
+ /* We are not the client */
+ self->is_client = tsk_false;
+
+ return 0;
+}
+
+/* Started -> (Too Small) -> Started */
+int s0000_Started_2_Started_X_iINVITE(va_list *app)
+{
+ tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
+
+ /* We are not the client */
+ self->is_client = tsk_false;
+
+ return 0;
+}
+
+/* Started -> (non-100rel and non-QoS, referred to as "basic") -> Ringing */
+int s0000_Started_2_Ringing_X_iINVITE(va_list *app)
+{
+ tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
+ tsip_request_t *request = va_arg(*app, tsip_request_t *);
+ const tsip_header_Session_Expires_t* hdr_SessionExpires;
+
+ /* we are not the client */
+ self->is_client = tsk_false;
+
+ /* update last INVITE */
+ TSK_OBJECT_SAFE_FREE(self->last_iInvite);
+ self->last_iInvite = tsk_object_ref(request);
+
+ // add "require:100rel" tag if the incoming INVITE contains "100rel" tag in "supported" header
+ if(self->last_iInvite && (tsip_message_supported(self->last_iInvite, "100rel") || tsip_message_required(self->last_iInvite, "100rel")) && self->supported._100rel){
+ self->required._100rel = tsk_true;
+ }
+
+ // add "require:timer" tag if incoming INVITE contains "timer" tag in "supported" header and session timers is enabled
+ if(TSIP_DIALOG_GET_SS(self)->media.timers.timeout){
+ if((hdr_SessionExpires = (const tsip_header_Session_Expires_t*)tsip_message_get_header(request, tsip_htype_Session_Expires))){
+ // "hdr_SessionExpires->delta_seconds" smallnest already checked
+ self->stimers.timer.timeout = hdr_SessionExpires->delta_seconds;
+ tsk_strupdate(&self->stimers.refresher, hdr_SessionExpires->refresher_uas ? "uas" : "uac");
+ self->stimers.is_refresher = tsk_striequals(self->stimers.refresher, "uas");
+ self->required.timer = tsk_true;
+ }
+ }
+
+ /* update state */
+ tsip_dialog_update_2(TSIP_DIALOG(self), request);
+
+ /* send Ringing */
+ /*if(TSIP_DIALOG_GET_STACK(self)->network.mode != tsip_stack_mode_webrtc2sip)*/{
+ send_RESPONSE(self, request, 180, "Ringing", tsk_false);
+ }
+
+ /* alert the user (session) */
+ TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_newcall,
+ tsip_event_code_dialog_request_incoming, "Incoming Call", request);
+
+ return 0;
+}
+
+/* Started -> (QoS (preconditions)) -> InProgress */
+int s0000_Started_2_InProgress_X_iINVITE(va_list *app)
+{
+ tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
+ tsip_request_t *request = va_arg(*app, tsip_request_t *);
+
+ /* We are not the client */
+ self->is_client = tsk_false;
+
+ /* update last INVITE */
+ TSK_OBJECT_SAFE_FREE(self->last_iInvite);
+ self->last_iInvite = tsk_object_ref(request);
+
+ /* Update state */
+ tsip_dialog_update_2(TSIP_DIALOG(self), request);
+
+ /* Send In Progress
+ RFC 3262 - 3 UAS Behavior
+
+ The provisional response to be sent reliably is constructed by the
+ UAS core according to the procedures of Section 8.2.6 of RFC 3261.
+ In addition, it MUST contain a Require header field containing the
+ option tag 100rel, and MUST include an RSeq header field. The value
+ of the header field for the first reliable provisional response in a
+ transaction MUST be between 1 and 2**31 - 1.
+ */
+ self->rseq = (rand() ^ rand()) % (0x00000001 << 31);
+ self->required._100rel = tsk_true;
+ self->required.precondition = (tsip_message_supported(self->last_iInvite, "precondition") || tsip_message_required(self->last_iInvite, "precondition"));
+ send_RESPONSE(self, request, 183, "Session in Progress", tsk_true);
+
+ return 0;
+}
+
+/* InProgress ->(iPRACK with QoS) -> InProgress */
+int s0000_InProgress_2_InProgress_X_iPRACK(va_list *app)
+{
+ int ret;
+
+ tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
+ tsip_request_t *request = va_arg(*app, tsip_request_t *);
+
+ /* Cancel 100rel timer */
+ TSIP_DIALOG_TIMER_CANCEL(100rel);
+
+ /* In all cases: Send 2xx PRACK */
+ if(!(ret = send_RESPONSE(self, request, 200, "OK", tsk_false))){
+ ++self->rseq;
+ }
+
+ /*
+ 1. Alice sends an initial INVITE without offer
+ 2. Bob's answer is sent in the first reliable provisional response, in this case it's a 1xx INVITE response
+ 3. Alice's answer is sent in the PRACK response
+ */
+ if(!self->msession_mgr->sdp.ro){
+ if(TSIP_MESSAGE_HAS_CONTENT(request)){
+ if((ret = tsip_dialog_invite_process_ro(self, request))){
+ /* Send Error and break the FSM */
+ ret = send_ERROR(self, self->last_iInvite, 488, "Not Acceptable", "SIP; cause=488; text=\"Bad content\"");
+ return -4;
+ }
+ }
+ else{
+ /* 488 INVITE */
+ ret = send_ERROR(self, self->last_iInvite, 488, "Not Acceptable", "SIP; cause=488; text=\"Offer expected in the PRACK\"");
+ return -3;
+ }
+ }
+
+ return ret;
+}
+
+/* InProgress ->(iPRACK without QoS) -> Ringing */
+int s0000_InProgress_2_Ringing_X_iPRACK(va_list *app)
+{
+ int ret;
+
+ tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
+ tsip_request_t *request = va_arg(*app, tsip_request_t *);
+
+ /* Cancel 100rel timer */
+ TSIP_DIALOG_TIMER_CANCEL(100rel);
+
+ /* In all cases: Send 2xx PRACK */
+ if(!(ret = send_RESPONSE(self, request, 200, "OK", tsk_false))){
+ ++self->rseq;
+ }
+
+ /*
+ 1. Alice sends an initial INVITE without offer
+ 2. Bob's answer is sent in the first reliable provisional response, in this case it's a 1xx INVITE response
+ 3. Alice's answer is sent in the PRACK response
+ */
+ if(self->msession_mgr && !self->msession_mgr->sdp.ro){
+ if(TSIP_MESSAGE_HAS_CONTENT(request)){
+ if((ret = tsip_dialog_invite_process_ro(self, request))){
+ /* Send Error and break the FSM */
+ ret = send_ERROR(self, self->last_iInvite, 488, "Not Acceptable", "SIP; cause=488; text=\"Bad content\"");
+ return -4;
+ }
+ }
+ else{
+ /* 488 INVITE */
+ ret = send_ERROR(self, self->last_iInvite, 488, "Not Acceptable", "SIP; cause=488; text=\"Offer expected in the PRACK\"");
+ return -3;
+ }
+ }
+
+ /* Send Ringing */
+ /*if(TSIP_DIALOG_GET_STACK(self)->network.mode != tsip_stack_mode_webrtc2sip)*/{
+ ret = send_RESPONSE(self, self->last_iInvite, 180, "Ringing", tsk_false);
+ }
+
+ /* Alert the user (session) */
+ TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_newcall,
+ tsip_event_code_dialog_request_incoming, "Incoming Call", request);
+
+ return ret;
+}
+
+/* InProgress ->(iUPDATE but cannot resume) -> InProgress */
+int s0000_InProgress_2_InProgress_X_iUPDATE(va_list *app)
+{
+ int ret;
+
+ tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
+ tsip_request_t *request = va_arg(*app, tsip_request_t *);
+
+ if((ret = tsip_dialog_invite_process_ro(self, request))){
+ /* Send Error and break the FSM */
+ ret = send_ERROR(self, request, 488, "Not Acceptable", "SIP; cause=488; text=\"Bad content\"");
+ return -4;
+ }
+ else{
+ // force SDP in 200 OK even if the request has the same SDP version
+ tsk_bool_t force_sdp = TSIP_MESSAGE_HAS_CONTENT(request);
+ ret = send_RESPONSE(self, request, 200, "OK",
+ (self->msession_mgr && (force_sdp || self->msession_mgr->ro_changed || self->msession_mgr->state_changed)));
+ }
+
+ return ret;
+}
+
+/* InProgress ->(iUPDATE can resume) -> Ringing */
+int s0000_InProgress_2_Ringing_X_iUPDATE(va_list *app)
+{
+ int ret;
+
+ tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
+ tsip_request_t *request = va_arg(*app, tsip_request_t *);
+ tsk_bool_t force_sdp;
+
+ if((ret = tsip_dialog_invite_process_ro(self, request))){
+ /* Send Error and break the FSM */
+ ret = send_ERROR(self, request, 488, "Not Acceptable", "SIP; cause=488; text=\"Bad content\"");
+ return -4;
+ }
+
+ /* Send 200 UPDATE */
+ // force SDP in 200 OK even if the request has the same SDP version
+ force_sdp = TSIP_MESSAGE_HAS_CONTENT(request);
+ ret = send_RESPONSE(self, request, 200, "OK",
+ (self->msession_mgr && (force_sdp || self->msession_mgr->ro_changed || self->msession_mgr->state_changed)));
+
+ /* Send Ringing */
+ /*if(TSIP_DIALOG_GET_STACK(self)->network.mode != tsip_stack_mode_webrtc2sip)*/{
+ ret = send_RESPONSE(self, self->last_iInvite, 180, "Ringing", tsk_false);
+ }
+
+ /* alert the user */
+ TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_newcall,
+ tsip_event_code_dialog_request_incoming, "Incoming Call", request);
+
+ return ret;
+}
+
+/* InProgress ->(iCANCEL) -> Terminated */
+int s0000_Inprogress_2_Terminated_X_iCANCEL(va_list *app)
+{
+ tsip_response_t* response;
+ int ret = -1;
+
+ tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
+ tsip_request_t *request = va_arg(*app, tsip_request_t *);
+
+ /* Send 2xx for the CANCEL (Direct to Transport layer beacause CANCEL is a special case) */
+ if((response = tsip_dialog_response_new(TSIP_DIALOG(self), 200, "OK", request))){
+ ret = tsip_transport_layer_send(TSIP_DIALOG_GET_STACK(self)->layer_transport, tsk_null, response);
+ TSK_OBJECT_SAFE_FREE(response);
+ }
+
+ /* Send Request Cancelled */
+ ret = send_ERROR(self, self->last_iInvite, 487, "Request Cancelled", "SIP; cause=487; text=\"Request Cancelled\"");
+
+ /* set last error (or info) */
+ tsip_dialog_set_lasterror(TSIP_DIALOG(self), "Call Cancelled", tsip_event_code_dialog_terminated);
+
+ /* alert the user */
+ TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_request,
+ tsip_event_code_dialog_request_incoming, "Incoming Request.", request);
+
+ return ret;
+}
+
+/* Ringing -> (iPRACK) -> Ringing */
+int s0000_Ringing_2_Ringing_X_iPRACK(va_list *app)
+{
+ int ret;
+
+ tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
+ tsip_request_t *request = va_arg(*app, tsip_request_t *);
+
+ if(!self->last_iInvite){
+ /* silently ignore */
+ return 0;
+ }
+
+ /* Cancel 100rel timer */
+ TSIP_DIALOG_TIMER_CANCEL(100rel);
+
+ /* Send 2xx PRACK */
+ ret = send_RESPONSE(self, request, 200, "OK", tsk_false);
+
+ /* alert the user */
+ TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_request,
+ tsip_event_code_dialog_request_incoming, "Incoming Request.", request);
+
+ return ret;
+}
+
+/* Ringing -> (oAccept) -> Connected */
+int s0000_Ringing_2_Connected_X_Accept(va_list *app)
+{
+ int ret;
+
+ tsip_dialog_invite_t *self;
+ const tsip_action_t* action;
+ tsk_bool_t mediaType_changed;
+
+ self = va_arg(*app, tsip_dialog_invite_t *);
+ va_arg(*app, const tsip_message_t *);
+ action = va_arg(*app, const tsip_action_t *);
+
+ /* Determine whether the remote party support UPDATE */
+ self->support_update = tsip_message_allowed(self->last_iInvite, "UPDATE");
+
+ /* Get Media type from the action */
+ mediaType_changed = (TSIP_DIALOG_GET_SS(self)->media.type != action->media.type && action->media.type != tmedia_none);
+ if(self->msession_mgr && mediaType_changed){
+ ret = tmedia_session_mgr_set_media_type(self->msession_mgr, action->media.type);
+ }
+
+ /* Appy media params received from the user */
+ if(!TSK_LIST_IS_EMPTY(action->media.params)){
+ ret = tmedia_session_mgr_set_3(self->msession_mgr, action->media.params);
+ }
+
+ /* set MSRP callback */
+ if((self->msession_mgr->type & tmedia_msrp) == tmedia_msrp){
+ ret = tmedia_session_mgr_set_msrp_cb(self->msession_mgr, TSIP_DIALOG_GET_SS(self)->userdata, TSIP_DIALOG_GET_SS(self)->media.msrp.callback);
+ }
+
+ /* Cancel 100rel timer */
+ TSIP_DIALOG_TIMER_CANCEL(100rel);
+
+ /* send 2xx OK */
+ ret = send_RESPONSE(self, self->last_iInvite, 200, "OK", tsk_true);
+
+ /* say we're waiting for the incoming ACK */
+ self->is_initial_iack_pending = tsk_true;
+
+ /* do not start the session until we get the ACK message
+ * http://code.google.com/p/doubango/issues/detail?id=157
+ */
+ /* do not start the session until we get at least one remote SDP
+ * https://code.google.com/p/doubango/issues/detail?id=438
+ */
+ // FIXME: (chrome) <-RTCWeb Breaker-> (chrome) do not work if media session is not started on i200
+ // http://code.google.com/p/webrtc2sip/issues/detail?id=45
+ if(/*TSIP_DIALOG_GET_STACK(self)->network.mode == tsip_stack_mode_webrtc2sip*/ TSIP_MESSAGE_HAS_CONTENT(self->last_iInvite)){
+ ret = tsip_dialog_invite_msession_start(self);
+ }
+
+ /* Session Timers */
+ if(self->stimers.timer.timeout){
+ if(self->stimers.is_refresher){
+ /* RFC 4028 - 9. UAS Behavior
+ It is RECOMMENDED that this refresh be sent oncehalf the session interval has elapsed.
+ Additional procedures for this refresh are described in Section 10.
+ */
+ tsip_dialog_invite_stimers_schedule(self, (self->stimers.timer.timeout*1000)/2);
+ }
+ else{
+ tsip_dialog_invite_stimers_schedule(self, (self->stimers.timer.timeout*1000));
+ }
+ }
+
+ /* alert the user (dialog) */
+ TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_connected, "Dialog connected");
+
+ return ret;
+}
+
+/* Ringing -> (oReject) -> Terminated */
+int s0000_Ringing_2_Terminated_X_Reject(va_list *app)
+{
+ int ret;
+ short code;
+ const char* phrase;
+ char* reason = tsk_null;
+
+ tsip_dialog_invite_t *self;
+ const tsip_action_t* action;
+
+ self = va_arg(*app, tsip_dialog_invite_t *);
+ va_arg(*app, const tsip_message_t *);
+ action = va_arg(*app, const tsip_action_t *);
+
+ /* Cancel 100rel timer */
+ TSIP_DIALOG_TIMER_CANCEL(100rel);
+
+ /* Send Reject */
+ code = action->line_resp.code>=300 ? action->line_resp.code : 603;
+ phrase = action->line_resp.phrase ? action->line_resp.phrase : "Decline";
+ tsk_sprintf(&reason, "SIP; cause=%hi; text=\"%s\"", code, phrase);
+ ret = send_ERROR(self, self->last_iInvite, code, phrase, reason);
+ TSK_FREE(reason);
+
+ /* set last error (or info) */
+ tsip_dialog_set_lasterror(TSIP_DIALOG(self), "Call Terminated", tsip_event_code_dialog_terminated);
+
+ return ret;
+}
+
+/* Ringing ->(iCANCEL) -> Terminated */
+int s0000_Ringing_2_Terminated_X_iCANCEL(va_list *app)
+{
+ int ret;
+ tsip_response_t* response;
+
+ tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
+ tsip_request_t *request = va_arg(*app, tsip_request_t *);
+
+ if(!self->last_iInvite){
+ /* silently ignore */
+ return 0;
+ }
+
+ /* Send 2xx for the CANCEL (Direct to Transport layer beacause CANCEL is a special case) */
+ if((response = tsip_dialog_response_new(TSIP_DIALOG(self), 200, "OK", request))){
+ ret = tsip_transport_layer_send(TSIP_DIALOG_GET_STACK(self)->layer_transport, tsk_null, response);
+ TSK_OBJECT_SAFE_FREE(response);
+ }
+
+ /* Send Request Cancelled */
+ ret = send_ERROR(self, self->last_iInvite, 487, "Request Cancelled", "SIP; cause=487; text=\"Request Cancelled\"");
+
+ /* set last error (or info) */
+ tsip_dialog_set_lasterror(TSIP_DIALOG(self), "Call Cancelled", tsip_event_code_dialog_terminated);
+
+ /* alert the user */
+ TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_request,
+ tsip_event_code_dialog_request_incoming, "Incoming Request.", request);
+
+ return ret;
+}
+
+/* Any ->(timer 100rel) -> Any */
+int s0000_Any_2_Any_X_timer100rel(va_list *app)
+{
+ tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
+
+ int ret;
+
+ if(!self->last_o1xxrel){
+ /* silently ignore */
+ return 0;
+ }
+
+ /* resync timer */
+ if((self->timer100rel.timeout *= 2) >= (64 * tsip_timers_getA())){
+ TSK_DEBUG_ERROR("Sending reliable 1xx failed");
+ return -2;
+ }
+
+ /* resend reliable 1xx */
+ if((ret = tsip_dialog_response_send(TSIP_DIALOG(self), self->last_o1xxrel))){
+ return ret;
+ }
+ else{
+ /* schedule timer */
+ TSIP_DIALOG_INVITE_TIMER_SCHEDULE(100rel);
+ }
+
+ return ret;
+}
+
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+// == STATE MACHINE END ==
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+int send_UNSUPPORTED(tsip_dialog_invite_t* self, const tsip_request_t* request, const char* option)
+{
+ tsip_response_t *response;
+
+ if(!self || !option){
+ TSK_DEBUG_ERROR("Invalid parameter");
+ return -1;
+ }
+
+ if((response = tsip_dialog_response_new(TSIP_DIALOG(self), 420, "Bad Extension", request))){
+ // Add UnSupported header
+ tsip_message_add_headers(response,
+ TSIP_HEADER_DUMMY_VA_ARGS("Unsupported", option),
+ TSIP_HEADER_DUMMY_VA_ARGS("Reason", "SIP; cause=420; text=\"Bad Extension\""),
+ tsk_null
+ );
+
+ tsip_dialog_response_send(TSIP_DIALOG(self), response);
+ TSK_OBJECT_SAFE_FREE(response);
+ }
+ return 0;
+}
+
+
OpenPOWER on IntegriCloud