808 lines
26 KiB
C
808 lines
26 KiB
C
/*
|
|
* Copyright (C) 2009 Mamadou Diop.
|
|
*
|
|
* Contact: Mamadou Diop <diopmamadou(at)doubango.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.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.org>
|
|
*
|
|
* @date Created: Sat Nov 8 16:54:58 2009 mdiop
|
|
*/
|
|
#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_RAck.h"
|
|
#include "tinysip/headers/tsip_header_RSeq.h"
|
|
|
|
#include "tsk_debug.h"
|
|
|
|
// http://cdnet.stpi.org.tw/techroom/market/_pdf/2009/eetelecomm_09_009_OneVoiceProfile.pdf
|
|
// 3GPP TS 26.114 (MMTel): Media handling and interaction
|
|
// 3GPP TS 24.173 (MMTel): Supplementary Services
|
|
//
|
|
/* ======================== MMTel Supplementary Services ========================
|
|
3GPP TS 24.607 : Originating Identification Presentation
|
|
3GPP TS 24.608 : Terminating Identification Presentation
|
|
3GPP TS 24.607 : Originating Identification Restriction
|
|
3GPP TS 24.608 : Terminating Identification Restriction
|
|
|
|
3GPP TS 24.604 : Communication Diversion Unconditional
|
|
3GPP TS 24.604 : Communication Diversion on not Logged
|
|
3GPP TS 24.604 : Communication Diversion on Busy
|
|
3GPP TS 24.604 : Communication Diversion on not Reachable
|
|
3GPP TS 24.604 : Communication Diversion on No Reply
|
|
3GPP TS 24.611 : Barring of All Incoming Calls
|
|
3GPP TS 24.611 : Barring of All Outgoing Calls
|
|
3GPP TS 24.611 : Barring of Outgoing International Calls
|
|
3GPP TS 24.611 : Barring of Incoming Calls - When Roaming
|
|
3GPP TS 24.610 : Communication Hold
|
|
3GPP TS 24.606 : Message Waiting Indication
|
|
3GPP TS 24.615 : Communication Waiting
|
|
3GPP TS 24.605 : Ad-Hoc Multi Party Conference
|
|
*/
|
|
|
|
/* ======================== internal functions ======================== */
|
|
int send_INVITE(tsip_dialog_invite_t *self);
|
|
int send_PRACK(tsip_dialog_invite_t *self, const tsip_response_t* r1xx);
|
|
int send_ACK(tsip_dialog_invite_t *self, const tsip_response_t* r2xxINVITE);
|
|
int send_BYE(tsip_dialog_invite_t *self);
|
|
int send_CANCEL(tsip_dialog_invite_t *self);
|
|
int tsip_dialog_invite_OnTerminated(tsip_dialog_invite_t *self);
|
|
|
|
/* ======================== transitions ======================== */
|
|
extern int c0000_Started_2_Outgoing_X_oINVITE(va_list *app);
|
|
extern int c0001_Outgoing_2_Connected_X_i2xxINVITE(va_list *app);
|
|
extern int c0002_Outgoing_2_Terminated_X_i300_to_i699INVITE(va_list *app);
|
|
extern int c0003_Outgoing_2_Terminated_X_oCANCEL(va_list *app);
|
|
|
|
extern int s0000_Started_2_Incoming_X_iINVITE(va_list *app);
|
|
extern int s0000_Incoming_2_Connected_X_o2xx(va_list *app); // 2xx INVITE
|
|
extern int s0000_Incoming_2_Terminated_X_oCANCEL(va_list *app);
|
|
|
|
int x0000_Any_2_Any_X_i2xxINVITE(va_list *app);
|
|
int x0000_Any_2_Trying_X_oBYE(va_list *app); /* If not Connected => Cancel will be called instead. See tsip_dialog_hangup() */
|
|
int x0000_Any_2_Trying_X_shutdown(va_list *app);
|
|
|
|
int x0000_Any_2_Trying_X_oINVITE(va_list *app);
|
|
int x0000_Any_2_Trying_X_oUPDATE(va_list *app);
|
|
int x0000_Any_2_Any_X_iINVITE(va_list *app);
|
|
int x0000_Any_2_Any_X_iUPDATE(va_list *app);
|
|
int x0000_Any_2_Any_X_i1xx(va_list *app);
|
|
int x0000_Any_2_Any_X_i2xx(va_list *app);
|
|
int x0000_Any_2_Any_X_i401_i407_i421_i494(va_list *app);
|
|
int x0000_Any_2_Any_X_iPRACK(va_list *app);
|
|
int x0000_Any_2_Any_X_iACK(va_list *app);
|
|
int x9998_Any_2_Any_X_transportError(va_list *app);
|
|
int x9999_Any_2_Any_X_Error(va_list *app);
|
|
|
|
|
|
/* ======================== conds ======================== */
|
|
tsk_bool_t _fsm_cond_is_resp2INVITE(tsip_dialog_invite_t* self, tsip_message_t* message)
|
|
{
|
|
if(message->CSeq){
|
|
return tsk_striequals(TSIP_MESSAGE_CSEQ_METHOD(message), "INVITE");
|
|
}
|
|
return tsk_false;
|
|
}
|
|
|
|
tsk_bool_t _fsm_cond_is_resp2UPDATE(tsip_dialog_invite_t* self, tsip_message_t* message)
|
|
{
|
|
if(message->CSeq){
|
|
return tsk_striequals(TSIP_MESSAGE_CSEQ_METHOD(message), "UPDATE");
|
|
}
|
|
return tsk_false;
|
|
}
|
|
|
|
tsk_bool_t _fsm_cond_is_resp2BYE(tsip_dialog_invite_t* self, tsip_message_t* message)
|
|
{
|
|
if(message->CSeq){
|
|
return tsk_striequals(TSIP_MESSAGE_CSEQ_METHOD(message), "BYE");
|
|
}
|
|
return tsk_false;
|
|
}
|
|
|
|
tsk_bool_t _fsm_cond_is_resp2PRACK(tsip_dialog_invite_t* self, tsip_message_t* message)
|
|
{
|
|
if(message->CSeq){
|
|
return tsk_striequals(TSIP_MESSAGE_CSEQ_METHOD(message), "PRACK");
|
|
}
|
|
return tsk_false;
|
|
}
|
|
|
|
/* ======================== actions ======================== */
|
|
/* #include "tinysip/dialogs/tsip_dialog_invite.common.h" */
|
|
|
|
/* ======================== states ======================== */
|
|
/* #include "tinysip/dialogs/tsip_dialog_invite.common.h" */
|
|
|
|
|
|
|
|
/* 3GPP TS 24.610 : Communication Hold */
|
|
extern int tsip_dialog_invite_hold_init(tsip_dialog_invite_t *self);
|
|
|
|
int tsip_dialog_invite_event_callback(const tsip_dialog_invite_t *self, tsip_dialog_event_type_t type, const tsip_message_t *msg)
|
|
{
|
|
int ret = -1;
|
|
|
|
switch(type)
|
|
{
|
|
case tsip_dialog_i_msg:
|
|
{
|
|
if(msg){
|
|
if(TSIP_MESSAGE_IS_RESPONSE(msg)){ /* Response */
|
|
if(TSIP_RESPONSE_IS_1XX(msg)){ // 100-199
|
|
ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_i1xx, msg, tsk_null);
|
|
}
|
|
else if(TSIP_RESPONSE_IS_2XX(msg)){ // 200-299
|
|
ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_i2xx, msg, tsk_null);
|
|
}
|
|
else if(TSIP_RESPONSE_CODE(msg) == 401 || TSIP_RESPONSE_CODE(msg) == 407 || TSIP_RESPONSE_CODE(msg) == 421 || TSIP_RESPONSE_CODE(msg) == 494){ // 401,407,421,494
|
|
ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_i401_i407_i421_i494, msg, tsk_null);
|
|
}
|
|
else if(TSIP_RESPONSE_IS_3456(msg)){ // 300-699
|
|
ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_i300_to_i699, msg, tsk_null);
|
|
}
|
|
else; // Ignore
|
|
}
|
|
else{ /* Request */
|
|
if(TSIP_REQUEST_IS_INVITE(msg)){ // INVITE
|
|
ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_iINVITE, msg, tsk_null);
|
|
}
|
|
else if(TSIP_REQUEST_IS_UPDATE(msg)){ // UPDATE
|
|
ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_iUPDATE, msg, tsk_null);
|
|
}
|
|
else if(TSIP_REQUEST_IS_PRACK(msg)){ // PRACK
|
|
ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_iPRACK, msg, tsk_null);
|
|
}
|
|
else if(TSIP_REQUEST_IS_ACK(msg)){ // ACK
|
|
ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_iACK, msg, tsk_null);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case tsip_dialog_canceled:
|
|
{
|
|
ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_oCANCEL, msg, tsk_null);
|
|
break;
|
|
}
|
|
|
|
case tsip_dialog_terminated:
|
|
case tsip_dialog_timedout:
|
|
case tsip_dialog_error:
|
|
case tsip_dialog_transport_error:
|
|
{
|
|
ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_transporterror, msg, tsk_null);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**Timer manager callback.
|
|
*
|
|
* @param self The owner of the signaled timer.
|
|
* @param timer_id The identifier of the signaled timer.
|
|
*
|
|
* @return Zero if succeed and non-zero error code otherwise.
|
|
**/
|
|
int tsip_dialog_invite_timer_callback(const tsip_dialog_invite_t* self, tsk_timer_id_t timer_id)
|
|
{
|
|
int ret = -1;
|
|
|
|
if(self){
|
|
if(timer_id == self->timerrefresh.id){
|
|
//ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_register, tsk_null, tsk_null);
|
|
}
|
|
else if(timer_id == self->timershutdown.id){
|
|
ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_shutdown_timedout, tsk_null, tsk_null);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
tsip_dialog_invite_t* tsip_dialog_invite_create(const tsip_ssession_handle_t* ss)
|
|
{
|
|
return tsk_object_new(tsip_dialog_invite_def_t, ss);
|
|
}
|
|
|
|
int tsip_dialog_invite_init(tsip_dialog_invite_t *self)
|
|
{
|
|
/* special cases (fsm) should be tried first */
|
|
|
|
/* 3GPP TS 24.610 : Communication Hold */
|
|
tsip_dialog_invite_hold_init(self);
|
|
|
|
/* Initialize the state machine (all other cases) */
|
|
tsk_fsm_set(TSIP_DIALOG_GET_FSM(self),
|
|
|
|
/*=======================
|
|
* === Started ===
|
|
*/
|
|
// Started -> (send INVITE) -> Outgoing
|
|
TSK_FSM_ADD_ALWAYS(_fsm_state_Started, _fsm_action_oINVITE, _fsm_state_Outgoing, c0000_Started_2_Outgoing_X_oINVITE, "c0000_Started_2_Outgoing_X_oINVITE"),
|
|
// Started -> (receive INVITE) -> Incoming
|
|
TSK_FSM_ADD_ALWAYS(_fsm_state_Started, _fsm_action_iINVITE, _fsm_state_Incoming, s0000_Started_2_Incoming_X_iINVITE, "s0000_Satrted_2_Incoming_X_iINVITE"),
|
|
// Started -> (Any) -> Started
|
|
TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Started, "tsip_dialog_invite_Started_2_Started_X_any"),
|
|
|
|
/*=======================
|
|
* === Outgoing (Client) ===
|
|
*/
|
|
// Outgoing -> (i2xx INVITE) -> Connected
|
|
TSK_FSM_ADD(_fsm_state_Outgoing, _fsm_action_i2xx, _fsm_cond_is_resp2INVITE, _fsm_state_Connected, c0001_Outgoing_2_Connected_X_i2xxINVITE, "c0001_Outgoing_2_Connected_X_i2xxINVITE"),
|
|
// Outgoing -> (300-699 INVITE) -> Termined
|
|
TSK_FSM_ADD(_fsm_state_Outgoing, _fsm_action_i300_to_i699, _fsm_cond_is_resp2INVITE, _fsm_state_Terminated, c0002_Outgoing_2_Terminated_X_i300_to_i699INVITE, "c0002_Outgoing_2_Terminated_X_i300_to_i699INVITE"),
|
|
// Outgoing -> (send CANCEL) -> Terminated
|
|
TSK_FSM_ADD_ALWAYS(_fsm_state_Outgoing, _fsm_action_oCANCEL, _fsm_state_Terminated, c0003_Outgoing_2_Terminated_X_oCANCEL, "c0003_Outgoing_2_Terminated_X_oCANCEL"),
|
|
|
|
|
|
/*=======================
|
|
* === BYE/SHUTDOWN ===
|
|
*/
|
|
// Any -> (oBYE) -> Trying
|
|
TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_oBYE, _fsm_state_Trying, x0000_Any_2_Trying_X_oBYE, "x0000_Any_2_Trying_X_oBYE"),
|
|
// Trying -> (i2xx BYE) -> Terminated
|
|
TSK_FSM_ADD(_fsm_state_Trying, _fsm_action_i2xx, _fsm_cond_is_resp2BYE, _fsm_state_Terminated, tsk_null, "x0000_Trying_2_Terminated_X_i2xxBYE"),
|
|
// Trying -> (i3xx-i6xx BYE) -> Terminated
|
|
TSK_FSM_ADD(_fsm_state_Trying, _fsm_action_i300_to_i699, _fsm_cond_is_resp2BYE, _fsm_state_Terminated, tsk_null, "x0000_Trying_2_Terminated_X_i2xxTOi6xxBYE"),
|
|
// Any -> (Shutdown) -> Trying
|
|
TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_oShutdown, _fsm_state_Trying, x0000_Any_2_Trying_X_shutdown, "x0000_Any_2_Trying_X_shutdown"),
|
|
// Any -> (shutdown timedout) -> Terminated
|
|
TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_shutdown_timedout, _fsm_state_Terminated, tsk_null, "tsip_dialog_invite_shutdown_timedout"),
|
|
|
|
|
|
/*=======================
|
|
* === Any ===
|
|
*/
|
|
// Any -> (i1xx) -> Any
|
|
TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_i1xx, tsk_fsm_state_any, x0000_Any_2_Any_X_i1xx, "x0000_Any_2_Any_X_i1xx"),
|
|
// Any -> (i401/407)
|
|
//
|
|
// Any -> (i2xx INVITE) -> Any
|
|
TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_i2xx, _fsm_cond_is_resp2INVITE, tsk_fsm_state_any, x0000_Any_2_Any_X_i2xxINVITE, "x0000_Any_2_Any_X_i2xxINVITE"),
|
|
// Any -> (transport error) -> Terminated
|
|
TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_transporterror, _fsm_state_Terminated, x9998_Any_2_Any_X_transportError, "x9998_Any_2_Any_X_transportError"),
|
|
// Any -> (transport error) -> Terminated
|
|
TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_error, _fsm_state_Terminated, x9999_Any_2_Any_X_Error, "x9999_Any_2_Any_X_Error"),
|
|
|
|
TSK_FSM_ADD_NULL());
|
|
|
|
/* Sets callback function */
|
|
TSIP_DIALOG(self)->callback = TSIP_DIALOG_EVENT_CALLBACK_F(tsip_dialog_invite_event_callback);
|
|
|
|
/* Timers */
|
|
//self->timerrefresh.id = TSK_INVALID_TIMER_ID;
|
|
//self->timerrefresh.timeout = ;
|
|
self->timershutdown.id = TSK_INVALID_TIMER_ID;
|
|
self->timershutdown.timeout = TSIP_DIALOG_SHUTDOWN_TIMEOUT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// start sending
|
|
int tsip_dialog_invite_start(tsip_dialog_invite_t *self)
|
|
{
|
|
int ret = -1;
|
|
if(self && !TSIP_DIALOG(self)->running){
|
|
if(!(ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_oINVITE, tsk_null, tsk_null))){
|
|
TSIP_DIALOG(self)->running = tsk_true;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------
|
|
// == STATE MACHINE BEGIN ==
|
|
//--------------------------------------------------------
|
|
|
|
|
|
int x0000_Any_2_Any_X_i2xxINVITE(va_list *app)
|
|
{
|
|
tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
|
|
const tsip_response_t *r2xx = va_arg(*app, const tsip_response_t *);
|
|
int ret = 0;
|
|
|
|
/* Update the dialog state */
|
|
if((ret = tsip_dialog_update(TSIP_DIALOG(self), r2xx))){
|
|
return ret;
|
|
}
|
|
|
|
/* send ACK */
|
|
return send_ACK(self, r2xx);
|
|
}
|
|
|
|
/* Any -> (oBYE) -> Trying */
|
|
int x0000_Any_2_Trying_X_oBYE(va_list *app)
|
|
{
|
|
tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
|
|
|
|
/* send BYE */
|
|
return send_BYE(self);
|
|
}
|
|
|
|
|
|
int x0000_Any_2_Trying_X_shutdown(va_list *app)
|
|
{
|
|
tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
|
|
|
|
/* schedule shutdow timeout */
|
|
TSIP_DIALOG_INVITE_TIMER_SCHEDULE(shutdown);
|
|
|
|
/* alert user */
|
|
TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_terminating, "Terminating dialog");
|
|
|
|
if(TSIP_DIALOG(self)->state == tsip_established){
|
|
return send_BYE(self);
|
|
}
|
|
else if(TSIP_DIALOG(self)->state == tsip_early){
|
|
return send_CANCEL(self);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int x0000_Any_2_Trying_X_oREINVITE(va_list *app)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int x0000_Any_2_Trying_X_oUPDATE(va_list *app)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int x0000_Any_2_Any_X_iREINVITE(va_list *app)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int x0000_Any_2_Any_X_iUPDATE(va_list *app)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int x0000_Any_2_Any_X_i1xx(va_list *app)
|
|
{
|
|
tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *);
|
|
const tsip_response_t *r1xx = va_arg(*app, const tsip_response_t *);
|
|
int ret = 0;
|
|
|
|
/* RFC 3262 - 4 UAC Behavior
|
|
If a provisional response is received for an initial request, and
|
|
that response contains a Require header field containing the option
|
|
tag 100rel, the response is to be sent reliably. If the response is
|
|
a 100 (Trying) (as opposed to 101 to 199), this option tag MUST be
|
|
ignored, and the procedures below MUST NOT be used.
|
|
*/
|
|
if((TSIP_RESPONSE_CODE(r1xx) >= 101 && TSIP_RESPONSE_CODE(r1xx) <=199) && tsip_message_required(r1xx, "100rel")){
|
|
if((ret = send_PRACK(self, r1xx))){
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int x0000_Any_2_Any_X_i2xx(va_list *app)
|
|
{
|
|
/* FIXME: if 2xxINVITE, then send ACK */
|
|
return 0;
|
|
}
|
|
|
|
int x0000_Any_2_Any_X_i401_i407_i421_i494(va_list *app)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int x0000_Any_2_Any_X_iPRACK(va_list *app)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int x0000_Any_2_Any_X_iACK(va_list *app)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int x9998_Any_2_Any_X_transportError(va_list *app)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int x9999_Any_2_Any_X_Error(va_list *app)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
// == STATE MACHINE END ==
|
|
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
|
|
// send INVITE request
|
|
int send_INVITE(tsip_dialog_invite_t *self)
|
|
{
|
|
int ret = -1;
|
|
tsip_request_t *request = tsk_null;
|
|
|
|
if(!self){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
goto bail;
|
|
}
|
|
|
|
if((request = tsip_dialog_request_new(TSIP_DIALOG(self), "INVITE"))){
|
|
|
|
/* apply action params to the request (will add a content if the action contains one) */
|
|
if(TSIP_DIALOG(self)->curr_action){
|
|
tsip_dialog_apply_action(request, TSIP_DIALOG(self)->curr_action);
|
|
}
|
|
|
|
/* add our payload if current action does not have one */
|
|
if(!TSIP_DIALOG(self)->curr_action || !TSIP_DIALOG(self)->curr_action->payload){
|
|
const tsdp_message_t* sdp_lo;
|
|
char* sdp;
|
|
if((sdp_lo = tmedia_session_mgr_get_lo(self->msession_mgr)) && (sdp = tsdp_message_tostring(sdp_lo))){
|
|
tsip_message_add_content(request, "application/sdp", sdp, tsk_strlen(sdp));
|
|
TSK_FREE(sdp);
|
|
}
|
|
}
|
|
|
|
/* send the request */
|
|
ret = tsip_dialog_request_send(TSIP_DIALOG(self), request);
|
|
if(ret == 0){
|
|
/* update last INVITE */
|
|
TSK_OBJECT_SAFE_FREE(self->last_invite);
|
|
self->last_invite = request;
|
|
}
|
|
else{
|
|
TSK_OBJECT_SAFE_FREE(request);
|
|
}
|
|
}
|
|
|
|
bail:
|
|
return ret;
|
|
}
|
|
|
|
int send_PRACK(tsip_dialog_invite_t *self, const tsip_response_t* r1xx)
|
|
{
|
|
// "Require: 100rel\r\n" should be checked by the caller of this function
|
|
int ret = -1;
|
|
tsip_request_t *request = tsk_null;
|
|
const tsip_header_RSeq_t* RSeq;
|
|
|
|
if(!self || !r1xx || !r1xx->CSeq){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
goto bail;
|
|
}
|
|
|
|
/* RFC 3262 - 4 UAC Behavior
|
|
Assuming the response is to be transmitted reliably, the UAC MUST
|
|
create a new request with method PRACK.
|
|
*/
|
|
if(!(request = tsip_dialog_request_new(TSIP_DIALOG(self), "PRACK"))){
|
|
goto bail;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* RFC 3262 - 4 UAC Behavior
|
|
The UAC MUST maintain a sequence number that indicates the most recently
|
|
received in-order reliable provisional response for the initial request.
|
|
*/
|
|
if((RSeq = (const tsip_header_RSeq_t*)tsip_message_get_header(r1xx, tsip_htype_RSeq))){
|
|
|
|
/* RFC 3262 - 4 UAC Behavior
|
|
If the UAC receives another reliable provisional
|
|
response to the same request, and its RSeq value is not one higher
|
|
than the value of the sequence number, that response MUST NOT be
|
|
acknowledged with a PRACK, and MUST NOT be processed further by the
|
|
UAC. An implementation MAY discard the response, or MAY cache the
|
|
response in the hopes of receiving the missing responses.
|
|
*/
|
|
if(self->rseq >= RSeq->seq){
|
|
TSK_DEBUG_WARN("1xx.RSeq value is not one higher than lastINVITE.RSeq.");
|
|
goto bail;
|
|
}
|
|
self->rseq = RSeq->seq;
|
|
}
|
|
|
|
/* RFC 3262 - 7.2 RAck
|
|
The first number is the value from the RSeq header in the provisional
|
|
response that is being acknowledged. The next number, and the
|
|
method, are copied from the CSeq in the response that is being
|
|
acknowledged. The method name in the RAck header is case sensitive.
|
|
*/
|
|
TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_RACK_VA_ARGS(self->rseq, r1xx->CSeq->seq, r1xx->CSeq->method));
|
|
|
|
ret = tsip_dialog_request_send(TSIP_DIALOG(self), request);
|
|
|
|
bail:
|
|
TSK_OBJECT_SAFE_FREE(request);
|
|
return ret;
|
|
}
|
|
|
|
int send_CANCEL(tsip_dialog_invite_t *self)
|
|
{
|
|
int ret = -1;
|
|
if(!self){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
goto bail;
|
|
}
|
|
/* RFC 3261 - 9 Canceling a Request
|
|
If the request being cancelled contains a Route header field, the
|
|
CANCEL request MUST include that Route header field's values.
|
|
==> up to tsip_dialog_request_new()
|
|
*/
|
|
|
|
/* RFC 3261 - 9 Canceling a Request
|
|
Once the CANCEL is constructed, the client SHOULD check whether it
|
|
has received any response (provisional or final) for the request
|
|
being cancelled (herein referred to as the "original request").
|
|
|
|
If no provisional response has been received, the CANCEL request MUST
|
|
NOT be sent; rather, the client MUST wait for the arrival of a
|
|
provisional response before sending the request.
|
|
==> up to the caller to check that we are not in the initial state and the FSM
|
|
is in Trying state.
|
|
*/
|
|
|
|
/* RFC 3261 - 9 Canceling a Request
|
|
The following procedures are used to construct a CANCEL request. The
|
|
Request-URI, Call-ID, To, the numeric part of CSeq, and From header
|
|
fields in the CANCEL request MUST be identical to those in the
|
|
request being cancelled, including tags. A CANCEL constructed by a
|
|
client MUST have only a single Via header field value matching the
|
|
top Via value in the request being cancelled. Using the same values
|
|
for these header fields allows the CANCEL to be matched with the
|
|
request it cancels (Section 9.2 indicates how such matching occurs).
|
|
However, the method part of the CSeq header field MUST have a value
|
|
of CANCEL. This allows it to be identified and processed as a
|
|
transaction in its own right (See Section 17)
|
|
*/
|
|
if(self->last_invite){
|
|
/* to avoid concurrent access, take a reference to the request */
|
|
tsip_request_t* last_invite = tsk_object_ref(self->last_invite);
|
|
tsip_request_t* cancel;
|
|
if((cancel = tsip_dialog_request_new(TSIP_DIALOG(self), "CANCEL"))){
|
|
/* Request-URI, Call-ID, To and From will be added by the dialog layer */
|
|
/* the numeric part of CSeq */
|
|
cancel->CSeq->seq = last_invite->CSeq->seq;
|
|
/* Via */
|
|
TSK_OBJECT_SAFE_FREE(cancel->firstVia); /* firstVia is already Null ...but who know? */
|
|
cancel->firstVia = tsk_object_ref(last_invite->firstVia); /* transport layer won't update the Via header */
|
|
|
|
ret = tsip_dialog_request_send(TSIP_DIALOG(self), cancel);
|
|
TSK_OBJECT_SAFE_FREE(cancel);
|
|
}
|
|
else{
|
|
TSK_DEBUG_ERROR("Failed to create CANCEL request");
|
|
ret = -2;
|
|
}
|
|
|
|
tsk_object_unref(last_invite);
|
|
return ret;
|
|
}
|
|
else{
|
|
TSK_DEBUG_WARN("There is no INVITE request to cancel");
|
|
return 0;
|
|
}
|
|
|
|
bail:
|
|
return ret;
|
|
}
|
|
|
|
int send_BYE(tsip_dialog_invite_t *self)
|
|
{
|
|
int ret = -1;
|
|
tsip_request_t *bye = tsk_null;
|
|
|
|
if(!self){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
goto bail;
|
|
}
|
|
/* RFC 3261 - 15.1.1 UAC Behavior
|
|
A BYE request is constructed as would any other request within a
|
|
dialog, as described in Section 12.
|
|
|
|
Once the BYE is constructed, the UAC core creates a new non-INVITE
|
|
client transaction, and passes it the BYE request. The UAC MUST
|
|
consider the session terminated (and therefore stop sending or
|
|
listening for media) as soon as the BYE request is passed to the
|
|
client transaction. If the response for the BYE is a 481
|
|
(Call/Transaction Does Not Exist) or a 408 (Request Timeout) or no
|
|
|
|
response at all is received for the BYE (that is, a timeout is
|
|
returned by the client transaction), the UAC MUST consider the
|
|
session and the dialog terminated.
|
|
*/
|
|
if((bye = tsip_dialog_request_new(TSIP_DIALOG(self), "BYE"))){
|
|
ret = tsip_dialog_request_send(TSIP_DIALOG(self), bye);
|
|
TSK_OBJECT_SAFE_FREE(bye);
|
|
}
|
|
|
|
bail:
|
|
return ret;
|
|
}
|
|
|
|
int send_ACK(tsip_dialog_invite_t *self, const tsip_response_t* r2xxINVITE)
|
|
{
|
|
int ret = -1;
|
|
tsip_request_t *request = tsk_null;
|
|
|
|
if(!self){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
goto bail;
|
|
}
|
|
|
|
if((request = tsip_dialog_request_new(TSIP_DIALOG(self), "ACK"))){
|
|
|
|
/* RFC 3261 - 13.2.2.4 2xx Responses
|
|
The UAC core MUST generate an ACK request for each 2xx received from
|
|
the transaction layer. The header fields of the ACK are constructed
|
|
in the same way as for any request sent within a dialog (see Section
|
|
12) with the exception of the CSeq and the header fields related to
|
|
authentication. The sequence number of the CSeq header field MUST be
|
|
the same as the INVITE being acknowledged, but the CSeq method MUST
|
|
be ACK. The ACK MUST contain the same credentials as the INVITE. If
|
|
the 2xx contains an offer (based on the rules above), the ACK MUST
|
|
carry an answer in its body. If the offer in the 2xx response is not
|
|
acceptable, the UAC core MUST generate a valid answer in the ACK and
|
|
then send a BYE immediately.
|
|
==> Credentials will be added by tsip_dialog_request_new() because they are
|
|
associated to the dialog itself.
|
|
==> It's up to us to add/update the CSeq number.
|
|
==> ACK requests sent here will create new client transactions, which means that
|
|
they will have there own branches. This is not the case for ACK requests sent from
|
|
the transaction layer.
|
|
*/
|
|
request->CSeq->seq = r2xxINVITE->CSeq->seq; /* As the 2xx has the same CSeq than the INVITE */
|
|
|
|
/* RFC 3261 - 13.2.2.4 2xx Responses
|
|
Once the ACK has been constructed, the procedures of [4] are used to
|
|
determine the destination address, port and transport. However, the
|
|
request is passed to the transport layer directly for transmission,
|
|
rather than a client transaction. This is because the UAC core
|
|
handles retransmissions of the ACK, not the transaction layer. The
|
|
ACK MUST be passed to the client transport every time a
|
|
retransmission of the 2xx final response that triggered the ACK
|
|
arrives.
|
|
*/
|
|
if(TSIP_DIALOG_GET_STACK(self)->layer_transport){
|
|
ret = tsip_transport_layer_send(TSIP_DIALOG_GET_STACK(self)->layer_transport, tsk_null, request);
|
|
}
|
|
else{
|
|
ret = -1;
|
|
TSK_DEBUG_ERROR("Not Transport layer associated to this stack");
|
|
}
|
|
TSK_OBJECT_SAFE_FREE(request);
|
|
}
|
|
|
|
bail:
|
|
return ret;
|
|
}
|
|
|
|
int tsip_dialog_invite_OnTerminated(tsip_dialog_invite_t *self)
|
|
{
|
|
TSK_DEBUG_INFO("=== INVITE Dialog terminated ===");
|
|
|
|
/* stop session manager */
|
|
if(self->msession_mgr){
|
|
tmedia_session_mgr_stop(self->msession_mgr);
|
|
}
|
|
|
|
/* alert the user */
|
|
TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_terminated, "Dialog terminated");
|
|
|
|
/* Remove from the dialog layer. */
|
|
return tsip_dialog_remove(TSIP_DIALOG(self));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//========================================================
|
|
// SIP dialog INVITE object definition
|
|
//
|
|
static tsk_object_t* tsip_dialog_invite_ctor(tsk_object_t * self, va_list * app)
|
|
{
|
|
tsip_dialog_invite_t *dialog = self;
|
|
if(dialog){
|
|
tsip_ssession_handle_t *ss = va_arg(*app, tsip_ssession_handle_t *);
|
|
|
|
/* Initialize base class */
|
|
tsip_dialog_init(TSIP_DIALOG(self), tsip_dialog_INVITE, tsk_null, ss, _fsm_state_Started, _fsm_state_Terminated);
|
|
|
|
/* FSM */
|
|
TSIP_DIALOG_GET_FSM(dialog)->debug = DEBUG_STATE_MACHINE;
|
|
tsk_fsm_set_callback_terminated(TSIP_DIALOG_GET_FSM(dialog), TSK_FSM_ONTERMINATED_F(tsip_dialog_invite_OnTerminated), (const void*)dialog);
|
|
|
|
/* Initialize the class itself */
|
|
tsip_dialog_invite_init(self);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
static tsk_object_t* tsip_dialog_invite_dtor(tsk_object_t * _self)
|
|
{
|
|
tsip_dialog_invite_t *self = _self;
|
|
if(self){
|
|
/* Cancel all timers */
|
|
TSIP_DIALOG_TIMER_CANCEL(refresh);
|
|
TSIP_DIALOG_TIMER_CANCEL(shutdown);
|
|
|
|
/* DeInitialize base class */
|
|
tsip_dialog_deinit(TSIP_DIALOG(self));
|
|
|
|
/* DeInitialize self */
|
|
TSK_OBJECT_SAFE_FREE(self->msession_mgr);
|
|
TSK_OBJECT_SAFE_FREE(self->last_invite);
|
|
//...
|
|
|
|
TSK_DEBUG_INFO("*** INVITE Dialog destroyed ***");
|
|
}
|
|
return self;
|
|
}
|
|
|
|
static int tsip_dialog_invite_cmp(const tsk_object_t *obj1, const tsk_object_t *obj2)
|
|
{
|
|
return tsip_dialog_cmp(obj1, obj2);
|
|
}
|
|
|
|
static const tsk_object_def_t tsip_dialog_invite_def_s =
|
|
{
|
|
sizeof(tsip_dialog_invite_t),
|
|
tsip_dialog_invite_ctor,
|
|
tsip_dialog_invite_dtor,
|
|
tsip_dialog_invite_cmp,
|
|
};
|
|
const tsk_object_def_t *tsip_dialog_invite_def_t = &tsip_dialog_invite_def_s;
|