diff options
Diffstat (limited to 'tinySIP/src/dialogs/tsip_dialog_invite.server.c')
-rw-r--r-- | tinySIP/src/dialogs/tsip_dialog_invite.server.c | 790 |
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; +} + + |