/* * Copyright (C) 2010-2011 Mamadou Diop. * * Contact: Mamadou Diop * * 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 * */ #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 && jvalue, 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; }