linphone-ios/coreapi/bellesip_sal/sal_op_call.c
2014-06-10 16:30:47 +02:00

938 lines
36 KiB
C

/*
linphone
Copyright (C) 2012 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "sal_impl.h"
#include "offeranswer.h"
static int extract_sdp(belle_sip_message_t* message,belle_sdp_session_description_t** session_desc, SalReason *error);
/*used for calls terminated before creation of a dialog*/
static void call_set_released(SalOp* op){
if (!op->call_released){
op->state=SalOpStateTerminated;
op->base.root->callbacks.call_released(op);
op->call_released=TRUE;
/*be aware that the following line may destroy the op*/
set_or_update_dialog(op,NULL);
}
}
static void call_set_error(SalOp* op,belle_sip_response_t* response){
sal_op_set_error_info_from_response(op,response);
op->base.root->callbacks.call_failure(op);
}
static void sdp_process(SalOp *h){
ms_message("Doing SDP offer/answer process of type %s",h->sdp_offering ? "outgoing" : "incoming");
if (h->result){
sal_media_description_unref(h->result);
}
h->result=sal_media_description_new();
if (h->sdp_offering){
offer_answer_initiate_outgoing(h->base.local_media,h->base.remote_media,h->result);
}else{
int i;
if (h->sdp_answer){
belle_sip_object_unref(h->sdp_answer);
}
offer_answer_initiate_incoming(h->base.local_media,h->base.remote_media,h->result,h->base.root->one_matching_codec);
h->sdp_answer=(belle_sdp_session_description_t *)belle_sip_object_ref(media_description_to_sdp(h->result));
/*once we have generated the SDP answer, we modify the result description for processing by the upper layer.
It should contains media parameters constraint from the remote offer, not our response*/
strcpy(h->result->addr,h->base.remote_media->addr);
h->result->bandwidth=h->base.remote_media->bandwidth;
for(i=0;i<sal_media_description_get_nb_active_streams(h->result);++i){
strcpy(h->result->streams[i].rtp_addr,h->base.remote_media->streams[i].rtp_addr);
h->result->streams[i].ptime=h->base.remote_media->streams[i].ptime;
h->result->streams[i].bandwidth=h->base.remote_media->streams[i].bandwidth;
h->result->streams[i].rtp_port=h->base.remote_media->streams[i].rtp_port;
strcpy(h->result->streams[i].rtcp_addr,h->base.remote_media->streams[i].rtcp_addr);
h->result->streams[i].rtcp_port=h->base.remote_media->streams[i].rtcp_port;
if ((h->result->streams[i].proto == SalProtoRtpSavpf) || (h->result->streams[i].proto == SalProtoRtpSavp)) {
h->result->streams[i].crypto[0] = h->base.remote_media->streams[i].crypto[0];
}
}
}
}
static int set_sdp(belle_sip_message_t *msg,belle_sdp_session_description_t* session_desc) {
belle_sip_header_content_type_t* content_type ;
belle_sip_header_content_length_t* content_length;
belle_sip_error_code error = BELLE_SIP_OK;
size_t length = 0;
char buff[2048];
if (session_desc) {
content_type = belle_sip_header_content_type_create("application","sdp");
error = belle_sip_object_marshal(BELLE_SIP_OBJECT(session_desc),buff,sizeof(buff),&length);
if (error != BELLE_SIP_OK) {
ms_error("Buffer too small or sdp too big");
return -1;
}
content_length= belle_sip_header_content_length_create(length);
belle_sip_message_add_header(msg,BELLE_SIP_HEADER(content_type));
belle_sip_message_add_header(msg,BELLE_SIP_HEADER(content_length));
belle_sip_message_set_body(msg,buff,length);
return 0;
} else {
return -1;
}
}
static int set_sdp_from_desc(belle_sip_message_t *msg, const SalMediaDescription *desc){
int err;
belle_sdp_session_description_t *sdp=media_description_to_sdp(desc);
err=set_sdp(msg,sdp);
belle_sip_object_unref(sdp);
return err;
}
static void call_process_io_error(void *user_ctx, const belle_sip_io_error_event_t *event){
SalOp* op=(SalOp*)user_ctx;
if (op->state==SalOpStateTerminated) return;
if (!op->dialog) {
/*call terminated very early*/
sal_error_info_set(&op->error_info,SalReasonIOError,503,"IO error",NULL);
op->base.root->callbacks.call_failure(op);
call_set_released(op);
} else {
/*dialog will terminated shortly, nothing to do*/
}
}
static void process_dialog_terminated(void *ctx, const belle_sip_dialog_terminated_event_t *event) {
SalOp* op=(SalOp*)ctx;
if (op->dialog && op->dialog==belle_sip_dialog_terminated_event_get_dialog(event)) {
/*belle_sip_transaction_t* trans=belle_sip_dialog_get_last_transaction(op->dialog);*/
ms_message("Dialog [%p] terminated for op [%p]",belle_sip_dialog_terminated_event_get_dialog(event),op);
switch(belle_sip_dialog_get_previous_state(op->dialog)) {
case BELLE_SIP_DIALOG_CONFIRMED:
if (op->state!=SalOpStateTerminated && op->state!=SalOpStateTerminating) {
/*this is probably a normal termination from a BYE*/
op->base.root->callbacks.call_terminated(op,op->dir==SalOpDirIncoming?sal_op_get_from(op):sal_op_get_to(op));
op->state=SalOpStateTerminating;
}
break;
default:
break;
}
belle_sip_main_loop_do_later(belle_sip_stack_get_main_loop(op->base.root->stack)
,(belle_sip_callback_t) call_set_released
, op);
} else {
ms_error("dialog unknown for op ");
}
}
static void handle_sdp_from_response(SalOp* op,belle_sip_response_t* response) {
belle_sdp_session_description_t* sdp;
SalReason reason;
if (op->base.remote_media){
sal_media_description_unref(op->base.remote_media);
op->base.remote_media=NULL;
}
if (extract_sdp(BELLE_SIP_MESSAGE(response),&sdp,&reason)==0) {
if (sdp){
op->base.remote_media=sal_media_description_new();
sdp_to_media_description(sdp,op->base.remote_media);
if (op->base.local_media) sdp_process(op);
}/*if no sdp in response, what can we do ?*/
}
}
static void cancelling_invite(SalOp* op ){
belle_sip_request_t* cancel;
ms_message("Cancelling INVITE request from [%s] to [%s] ",sal_op_get_from(op), sal_op_get_to(op));
cancel = belle_sip_client_transaction_create_cancel(op->pending_client_trans);
sal_op_send_request(op,cancel);
op->state=SalOpStateTerminating;
}
static int vfu_retry (void *user_data, unsigned int events) {
SalOp *op=(SalOp *)user_data;
sal_call_send_vfu_request(op);
sal_op_unref(op);
return BELLE_SIP_STOP;
}
static void call_process_response(void *op_base, const belle_sip_response_event_t *event){
SalOp* op = (SalOp*)op_base;
belle_sip_request_t* ack;
belle_sip_dialog_state_t dialog_state;
belle_sip_client_transaction_t* client_transaction = belle_sip_response_event_get_client_transaction(event);
belle_sip_request_t* req;
belle_sip_response_t* response=belle_sip_response_event_get_response(event);
int code = belle_sip_response_get_status_code(response);
belle_sip_header_content_type_t *header_content_type=NULL;
belle_sip_dialog_t *dialog=belle_sip_response_event_get_dialog(event);
if (!client_transaction) {
ms_warning("Discarding stateless response [%i] on op [%p]",code,op);
return;
}
req=belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(client_transaction));
set_or_update_dialog(op,dialog);
dialog_state=dialog ? belle_sip_dialog_get_state(dialog) : BELLE_SIP_DIALOG_NULL;
ms_message("Op [%p] receiving call response [%i], dialog is [%p] in state [%s]",op,code,dialog,belle_sip_dialog_state_to_string(dialog_state));
switch(dialog_state) {
case BELLE_SIP_DIALOG_NULL:
case BELLE_SIP_DIALOG_EARLY: {
if (strcmp("INVITE",belle_sip_request_get_method(req))==0 ) {
if (op->state == SalOpStateTerminating) {
/*check if CANCEL was sent before*/
if (strcmp("CANCEL",belle_sip_request_get_method(belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(op->pending_client_trans))))!=0) {
/*it wasn't sent */
if (code<200) {
cancelling_invite(op);
}else{
/* no need to send the INVITE because the UAS rejected the INVITE*/
if (op->dialog==NULL) call_set_released(op);
}
} else {
/*it was sent already, so just expect the 487 or any error response to send the call_released() notification*/
if (code>=300){
if (op->dialog==NULL) call_set_released(op);
}
}
} else if (code >= 180 && code<200) {
belle_sip_response_t *prev_response=belle_sip_object_data_get(BELLE_SIP_OBJECT(dialog),"early_response");
if (!prev_response || code>belle_sip_response_get_status_code(prev_response)){
handle_sdp_from_response(op,response);
op->base.root->callbacks.call_ringing(op);
}
belle_sip_object_data_set(BELLE_SIP_OBJECT(dialog),"early_response",belle_sip_object_ref(response),belle_sip_object_unref);
} else if (code>=300){
call_set_error(op,response);
if (op->dialog==NULL) call_set_released(op);
}
} else if (code >=200
&& code<300
&& strcmp("UPDATE",belle_sip_request_get_method(req))==0) {
handle_sdp_from_response(op,response);
op->base.root->callbacks.call_accepted(op);
}
}
break;
case BELLE_SIP_DIALOG_CONFIRMED: {
switch (op->state) {
case SalOpStateEarly:/*invite case*/
case SalOpStateActive: /*re-invite case*/
if (code >=200
&& code<300
&& strcmp("INVITE",belle_sip_request_get_method(req))==0) {
handle_sdp_from_response(op,response);
ack=belle_sip_dialog_create_ack(op->dialog,belle_sip_dialog_get_local_seq_number(op->dialog));
if (ack==NULL) {
ms_error("This call has been already terminated.");
return ;
}
if (op->sdp_answer){
set_sdp(BELLE_SIP_MESSAGE(ack),op->sdp_answer);
belle_sip_object_unref(op->sdp_answer);
op->sdp_answer=NULL;
}
belle_sip_dialog_send_ack(op->dialog,ack);
op->base.root->callbacks.call_accepted(op); /*INVITE*/
op->state=SalOpStateActive;
} else if (code >= 300 && strcmp("INVITE",belle_sip_request_get_method(req))==0){
call_set_error(op,response);
} else if (code == 491
&& strcmp("INFO",belle_sip_request_get_method(req)) == 0
&& (header_content_type = belle_sip_message_get_header_by_type(req,belle_sip_header_content_type_t))
&& strcmp("application",belle_sip_header_content_type_get_type(header_content_type))==0
&& strcmp("media_control+xml",belle_sip_header_content_type_get_subtype(header_content_type))==0) {
unsigned int retry_in =1000*((float)rand()/RAND_MAX);
belle_sip_source_t *s=sal_create_timer(op->base.root,vfu_retry,sal_op_ref(op), retry_in, "vfu request retry");
ms_message("Rejected vfu request on op [%p], just retry in [%ui] ms",op,retry_in);
belle_sip_object_unref(s);
}else {
/*ignoring*/
}
break;
case SalOpStateTerminating:
sal_op_send_request(op,belle_sip_dialog_create_request(op->dialog,"BYE"));
break;
case SalOpStateTerminated:
default:
ms_error("Call op [%p] receives unexpected answer [%i] while in state [%s].",op,code, sal_op_state_to_string(op->state));
}
}
break;
case BELLE_SIP_DIALOG_TERMINATED: {
if (code >= 300){
call_set_error(op,response);
}
}
break;
default: {
ms_error("call op [%p] receive answer [%i] not implemented",op,code);
}
break;
}
}
static void call_process_timeout(void *user_ctx, const belle_sip_timeout_event_t *event) {
SalOp* op=(SalOp*)user_ctx;
if (op->state==SalOpStateTerminated) return;
if (!op->dialog) {
/*call terminated very early*/
sal_error_info_set(&op->error_info,SalReasonRequestTimeout,408,"Request timeout",NULL);
op->base.root->callbacks.call_failure(op);
call_set_released(op);
} else {
/*dialog will terminated shortly, nothing to do*/
}
}
static void call_process_transaction_terminated(void *user_ctx, const belle_sip_transaction_terminated_event_t *event) {
SalOp* op = (SalOp*)user_ctx;
belle_sip_client_transaction_t *client_transaction=belle_sip_transaction_terminated_event_get_client_transaction(event);
belle_sip_server_transaction_t *server_transaction=belle_sip_transaction_terminated_event_get_server_transaction(event);
belle_sip_request_t* req;
belle_sip_response_t* resp;
if (client_transaction) {
req=belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(client_transaction));
resp=belle_sip_transaction_get_response(BELLE_SIP_TRANSACTION(client_transaction));
} else {
req=belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(server_transaction));
resp=belle_sip_transaction_get_response(BELLE_SIP_TRANSACTION(server_transaction));
}
if (op->state ==SalOpStateTerminating
&& strcmp("BYE",belle_sip_request_get_method(req))==0
&& (!resp || (belle_sip_response_get_status_code(resp) !=401
&& belle_sip_response_get_status_code(resp) !=407))) {
if (op->dialog==NULL) call_set_released(op);
}
}
static void call_terminated(SalOp* op,belle_sip_server_transaction_t* server_transaction, belle_sip_request_t* request,int status_code) {
belle_sip_response_t* resp;
op->base.root->callbacks.call_terminated(op,op->dir==SalOpDirIncoming?sal_op_get_from(op):sal_op_get_to(op));
resp=sal_op_create_response_from_request(op,request,status_code);
belle_sip_server_transaction_send_response(server_transaction,resp);
}
static void unsupported_method(belle_sip_server_transaction_t* server_transaction,belle_sip_request_t* request) {
belle_sip_response_t* resp;
resp=belle_sip_response_create_from_request(request,501);
belle_sip_server_transaction_send_response(server_transaction,resp);
return;
}
/*
* Extract the sdp from a sip message.
* If there is no body in the message, the session_desc is set to null, 0 is returned.
* If body was present is not a SDP or parsing of SDP failed, -1 is returned and SalReason is set appropriately.
*
**/
static int extract_sdp(belle_sip_message_t* message,belle_sdp_session_description_t** session_desc, SalReason *error) {
belle_sip_header_content_type_t* content_type=belle_sip_message_get_header_by_type(message,belle_sip_header_content_type_t);
if (content_type){
if (strcmp("application",belle_sip_header_content_type_get_type(content_type))==0
&& strcmp("sdp",belle_sip_header_content_type_get_subtype(content_type))==0) {
*session_desc=belle_sdp_session_description_parse(belle_sip_message_get_body(message));
if (*session_desc==NULL) {
ms_error("Failed to parse SDP message.");
*error=SalReasonNotAcceptable;
return -1;
}
}else{
*error=SalReasonUnsupportedContent;
return -1;
}
}else *session_desc=NULL;
return 0;
}
static int is_media_description_acceptable(SalMediaDescription *md){
if (md->nb_streams==0){
ms_warning("Media description does not define any stream.");
return FALSE;
}
return TRUE;
}
static int process_sdp_for_invite(SalOp* op,belle_sip_request_t* invite) {
belle_sdp_session_description_t* sdp;
int err=0;
SalReason reason;
if (extract_sdp(BELLE_SIP_MESSAGE(invite),&sdp,&reason)==0) {
if (sdp){
op->sdp_offering=FALSE;
op->base.remote_media=sal_media_description_new();
sdp_to_media_description(sdp,op->base.remote_media);
/*make some sanity check about the SDP received*/
if (!is_media_description_acceptable(op->base.remote_media)){
err=-1;
reason=SalReasonNotAcceptable;
}
belle_sip_object_unref(sdp);
}else op->sdp_offering=TRUE; /*INVITE without SDP*/
}else err=-1;
if (err==-1){
sal_call_decline(op,reason,NULL);
}
return err;
}
static void sal_op_reset_descriptions(SalOp *op) {
if (op->base.remote_media){
sal_media_description_unref(op->base.remote_media);
op->base.remote_media=NULL;
}
if (op->result){
sal_media_description_unref(op->result);
op->result=NULL;
}
}
static void process_request_event(void *op_base, const belle_sip_request_event_t *event) {
SalOp* op = (SalOp*)op_base;
belle_sip_server_transaction_t* server_transaction=NULL;
belle_sdp_session_description_t* sdp;
belle_sip_request_t* req = belle_sip_request_event_get_request(event);
belle_sip_dialog_state_t dialog_state;
belle_sip_response_t* resp;
belle_sip_header_t* call_info;
const char *method=belle_sip_request_get_method(req);
if (strcmp("ACK",method)!=0){ /*ACK does'nt create srv transaction*/
server_transaction = belle_sip_provider_create_server_transaction(op->base.root->prov,belle_sip_request_event_get_request(event));
belle_sip_object_ref(server_transaction);
belle_sip_transaction_set_application_data(BELLE_SIP_TRANSACTION(server_transaction),sal_op_ref(op));
}
if (strcmp("INVITE",method)==0) {
if (op->pending_server_trans) belle_sip_object_unref(op->pending_server_trans);
/*updating pending invite transaction*/
op->pending_server_trans=server_transaction;
belle_sip_object_ref(op->pending_server_trans);
}
if (strcmp("UPDATE",method)==0) {
if (op->pending_update_server_trans) belle_sip_object_unref(op->pending_update_server_trans);
/*updating pending update transaction*/
op->pending_update_server_trans=server_transaction;
belle_sip_object_ref(op->pending_update_server_trans);
}
if (!op->dialog) {
set_or_update_dialog(op,belle_sip_provider_create_dialog(op->base.root->prov,BELLE_SIP_TRANSACTION(op->pending_server_trans)));
ms_message("new incoming call from [%s] to [%s]",sal_op_get_from(op),sal_op_get_to(op));
}
dialog_state=belle_sip_dialog_get_state(op->dialog);
switch(dialog_state) {
case BELLE_SIP_DIALOG_NULL: {
if (strcmp("INVITE",method)==0) {
if (!op->replaces && (op->replaces=belle_sip_message_get_header_by_type(BELLE_SIP_MESSAGE(req),belle_sip_header_replaces_t))) {
belle_sip_object_ref(op->replaces);
} else if(op->replaces) {
ms_warning("replace header already set");
}
process_sdp_for_invite(op,req);
if ((call_info=belle_sip_message_get_header(BELLE_SIP_MESSAGE(req),"Call-Info"))) {
if( strstr(belle_sip_header_get_unparsed_value(call_info),"answer-after=") != NULL) {
op->auto_answer_asked=TRUE;
ms_message("The caller asked to automatically answer the call(Emergency?)\n");
}
}
op->base.root->callbacks.call_received(op);
break;
} /* else same behavior as for EARLY state*/
}
case BELLE_SIP_DIALOG_EARLY: {
//hmm probably a cancel
if (strcmp("CANCEL",method)==0) {
if(belle_sip_request_event_get_server_transaction(event)) {
/*first answer 200 ok to cancel*/
belle_sip_server_transaction_send_response(server_transaction
,sal_op_create_response_from_request(op,req,200));
/*terminate invite transaction*/
call_terminated(op
,op->pending_server_trans
,belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(op->pending_server_trans)),487);
} else {
/*call leg does not exist*/
belle_sip_server_transaction_send_response(server_transaction
,sal_op_create_response_from_request(op,req,481));
}
} else if (strcmp("PRACK",method)==0) {
resp=sal_op_create_response_from_request(op,req,200);
belle_sip_server_transaction_send_response(server_transaction,resp);
} else if (strcmp("UPDATE",method)==0) {
sal_op_reset_descriptions(op);
if (process_sdp_for_invite(op,req)==0)
op->base.root->callbacks.call_updating(op);
} else {
belle_sip_error("Unexpected method [%s] for dialog state BELLE_SIP_DIALOG_EARLY",belle_sip_request_get_method(req));
unsupported_method(server_transaction,req);
}
break;
}
case BELLE_SIP_DIALOG_CONFIRMED:
/*great ACK received*/
if (strcmp("ACK",method)==0) {
if (op->sdp_offering){
SalReason reason;
if (extract_sdp(BELLE_SIP_MESSAGE(req),&sdp,&reason)==0){
if (sdp){
if (op->base.remote_media)
sal_media_description_unref(op->base.remote_media);
op->base.remote_media=sal_media_description_new();
sdp_to_media_description(sdp,op->base.remote_media);
sdp_process(op);
belle_sip_object_unref(sdp);
}else{
ms_warning("SDP expected in ACK but not found.");
}
}
}
op->base.root->callbacks.call_ack(op);
} else if(strcmp("BYE",method)==0) {
resp=sal_op_create_response_from_request(op,req,200);
belle_sip_server_transaction_send_response(server_transaction,resp);
op->base.root->callbacks.call_terminated(op,op->dir==SalOpDirIncoming?sal_op_get_from(op):sal_op_get_to(op));
op->state=SalOpStateTerminating;
/*call end not notified by dialog deletion because transaction can end before dialog*/
} else if(strcmp("INVITE",method)==0) {
/*re-invite*/
sal_op_reset_descriptions(op);
if (process_sdp_for_invite(op,req)==0)
op->base.root->callbacks.call_updating(op);
} else if (strcmp("INFO",method)==0){
if (belle_sip_message_get_body(BELLE_SIP_MESSAGE(req))
&& strstr(belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)),"picture_fast_update")) {
/*vfu request*/
ms_message("Receiving VFU request on op [%p]",op);
if (op->base.root->callbacks.vfu_request){
op->base.root->callbacks.vfu_request(op);
}
}else{
SalBody salbody;
if (sal_op_get_body(op,(belle_sip_message_t*)req,&salbody)) {
if (sal_body_has_type(&salbody,"application","dtmf-relay")){
char tmp[10];
if (sal_lines_get_value(salbody.data, "Signal",tmp, sizeof(tmp))){
op->base.root->callbacks.dtmf_received(op,tmp[0]);
}
}else
op->base.root->callbacks.info_received(op,&salbody);
} else {
op->base.root->callbacks.info_received(op,NULL);
}
}
resp=sal_op_create_response_from_request(op,req,200);
belle_sip_server_transaction_send_response(server_transaction,resp);
}else if (strcmp("REFER",method)==0) {
sal_op_process_refer(op,event,server_transaction);
} else if (strcmp("NOTIFY",method)==0) {
sal_op_call_process_notify(op,event,server_transaction);
} else if (strcmp("OPTIONS",method)==0) {
resp=sal_op_create_response_from_request(op,req,200);
belle_sip_server_transaction_send_response(server_transaction,resp);
} else if (strcmp("CANCEL",method)==0) {
/*call leg does not exist because 200ok already sent*/
belle_sip_server_transaction_send_response(server_transaction,sal_op_create_response_from_request(op,req,481));
} else if (strcmp("MESSAGE",method)==0){
sal_process_incoming_message(op,event);
} else if (strcmp("UPDATE",method)==0) {
/*FIXME jehan: It might be better to silently accept UPDATE which do not modify either the number or the nature of streams*/
/*rfc 3311
* 5.2 Receiving an UPDATE
* ...
* If the UAS cannot change the session parameters without prompting the user, it SHOULD reject
* the request with a 504 response.
*/
resp=sal_op_create_response_from_request(op,req,504);
belle_sip_response_set_reason_phrase(resp,"Cannot change the session parameters without prompting the user");
/*belle_sip_message_add_header( BELLE_SIP_MESSAGE(resp)
,belle_sip_header_create( "Warning", "Cannot change the session parameters without prompting the user"));*/
belle_sip_server_transaction_send_response(server_transaction,resp);
return;
}else{
ms_error("unexpected method [%s] for dialog [%p]",belle_sip_request_get_method(req),op->dialog);
unsupported_method(server_transaction,req);
}
break;
default:
ms_error("unexpected dialog state [%s]",belle_sip_dialog_state_to_string(dialog_state));
break;
}
if (server_transaction) belle_sip_object_unref(server_transaction);
}
/*Call API*/
int sal_call_set_local_media_description(SalOp *op, SalMediaDescription *desc){
if (desc)
sal_media_description_ref(desc);
if (op->base.local_media)
sal_media_description_unref(op->base.local_media);
op->base.local_media=desc;
if (op->base.remote_media){
/*case of an incoming call where we modify the local capabilities between the time
* the call is ringing and it is accepted (for example if you want to accept without video*/
/*reset the sdp answer so that it is computed again*/
if (op->sdp_answer){
belle_sip_object_unref(op->sdp_answer);
op->sdp_answer=NULL;
}
}
return 0;
}
static belle_sip_header_allow_t *create_allow(bool_t enable_update){
belle_sip_header_allow_t* header_allow;
char allow [256];
snprintf(allow,sizeof(allow),"INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO%s",(enable_update?", UPDATE":""));
header_allow = belle_sip_header_allow_create(allow);
return header_allow;
}
static void sal_op_fill_invite(SalOp *op, belle_sip_request_t* invite) {
belle_sip_message_add_header(BELLE_SIP_MESSAGE(invite),BELLE_SIP_HEADER(create_allow(op->base.root->enable_sip_update)));
if (op->base.root->session_expires!=0){
belle_sip_message_add_header(BELLE_SIP_MESSAGE(invite),belle_sip_header_create( "Session-expires", "200"));
belle_sip_message_add_header(BELLE_SIP_MESSAGE(invite),belle_sip_header_create( "Supported", "timer"));
}
if (op->base.local_media){
op->sdp_offering=TRUE;
set_sdp_from_desc(BELLE_SIP_MESSAGE(invite),op->base.local_media);
}else op->sdp_offering=FALSE;
return;
}
int sal_call(SalOp *op, const char *from, const char *to){
belle_sip_request_t* invite;
op->dir=SalOpDirOutgoing;
sal_op_set_from(op,from);
sal_op_set_to(op,to);
ms_message("[%s] calling [%s] on op [%p]", from, to, op);
invite=sal_op_build_request(op,"INVITE");
sal_op_fill_invite(op,invite);
sal_op_call_fill_cbs(op);
if (op->replaces){
belle_sip_message_add_header(BELLE_SIP_MESSAGE(invite),BELLE_SIP_HEADER(op->replaces));
}
if (op->referred_by)
belle_sip_message_add_header(BELLE_SIP_MESSAGE(invite),BELLE_SIP_HEADER(op->referred_by));
return sal_op_send_request(op,invite);
}
static belle_sip_listener_callbacks_t call_op_callbacks={0};
void sal_op_call_fill_cbs(SalOp*op) {
if (call_op_callbacks.process_response_event==NULL){
call_op_callbacks.process_io_error=call_process_io_error;
call_op_callbacks.process_response_event=call_process_response;
call_op_callbacks.process_timeout=call_process_timeout;
call_op_callbacks.process_transaction_terminated=call_process_transaction_terminated;
call_op_callbacks.process_request_event=process_request_event;
call_op_callbacks.process_dialog_terminated=process_dialog_terminated;
}
op->callbacks=&call_op_callbacks;
op->type=SalOpCall;
}
static void handle_offer_answer_response(SalOp* op, belle_sip_response_t* response) {
if (op->base.local_media){
/*this is the case where we received an invite without SDP*/
if (op->sdp_offering) {
set_sdp_from_desc(BELLE_SIP_MESSAGE(response),op->base.local_media);
}else{
if (op->sdp_answer==NULL)
sdp_process(op);
if (op->sdp_answer){
set_sdp(BELLE_SIP_MESSAGE(response),op->sdp_answer);
belle_sip_object_unref(op->sdp_answer);
op->sdp_answer=NULL;
}
}
}else{
ms_error("You are accepting a call but not defined any media capabilities !");
}
}
int sal_call_notify_ringing(SalOp *op, bool_t early_media){
int status_code =early_media?183:180;
belle_sip_request_t* req=belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(op->pending_server_trans));
belle_sip_response_t* ringing_response = sal_op_create_response_from_request(op,req,status_code);
belle_sip_header_t *require;
const char *tags=NULL;
if (early_media){
handle_offer_answer_response(op,ringing_response);
}
require=belle_sip_message_get_header((belle_sip_message_t*)req,"Require");
if (require) tags=belle_sip_header_get_unparsed_value(require);
/* if client requires 100rel, then add necessary stuff*/
if (tags && strstr(tags,"100rel")!=0) {
belle_sip_message_add_header((belle_sip_message_t*)ringing_response,belle_sip_header_create("Require","100rel"));
belle_sip_message_add_header((belle_sip_message_t*)ringing_response,belle_sip_header_create("RSeq","1"));
}
#ifndef SAL_OP_CALL_FORCE_CONTACT_IN_RINGING
if (tags && strstr(tags,"100rel")!=0)
#endif
{
belle_sip_header_address_t* contact= (belle_sip_header_address_t*)sal_op_get_contact_address(op);
belle_sip_header_contact_t* contact_header;
if (contact && (contact_header=belle_sip_header_contact_create(contact))) {
belle_sip_message_add_header(BELLE_SIP_MESSAGE(ringing_response),BELLE_SIP_HEADER(contact_header));
}
}
belle_sip_server_transaction_send_response(op->pending_server_trans,ringing_response);
return 0;
}
/*accept an incoming call or, during a call accept a reINVITE*/
int sal_call_accept(SalOp*h){
belle_sip_response_t *response;
belle_sip_header_contact_t* contact_header;
belle_sip_server_transaction_t* transaction;
/*first check if an UPDATE transaction need to be accepted*/
if (h->pending_update_server_trans) {
transaction=h->pending_update_server_trans;
} else if (h->pending_server_trans) {
/*so it must be an invite/re-invite*/
transaction=h->pending_server_trans;
} else {
ms_error("No transaction to accept for op [%p]",h);
return -1;
}
ms_message("Accepting server transaction [%p] on op [%p]", transaction, h);
/* sends a 200 OK */
response = sal_op_create_response_from_request(h,belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(transaction)),200);
if (response==NULL){
ms_error("Fail to build answer for call");
return -1;
}
belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),BELLE_SIP_HEADER(create_allow(h->base.root->enable_sip_update)));
if (h->base.root->session_expires!=0){
if (h->supports_session_timers) {
belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),belle_sip_header_create("Supported", "timer"));
}
}
if ((contact_header=sal_op_create_contact(h))) {
belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),BELLE_SIP_HEADER(contact_header));
}
_sal_op_add_custom_headers(h, BELLE_SIP_MESSAGE(response));
handle_offer_answer_response(h,response);
belle_sip_server_transaction_send_response(transaction,response);
if (h->pending_update_server_trans) {
belle_sip_object_unref(h->pending_update_server_trans);
h->pending_update_server_trans=NULL;
}
return 0;
}
int sal_call_decline(SalOp *op, SalReason reason, const char *redirection /*optional*/){
belle_sip_response_t* response;
belle_sip_header_contact_t* contact=NULL;
int status=sal_reason_to_sip_code(reason);
if (reason==SalReasonRedirect){
if (redirection!=NULL) {
if (strstr(redirection,"sip:")!=0) status=302;
else status=380;
contact= belle_sip_header_contact_new();
belle_sip_header_address_set_uri(BELLE_SIP_HEADER_ADDRESS(contact),belle_sip_uri_parse(redirection));
} else {
ms_error("Cannot redirect to null");
}
}
response = sal_op_create_response_from_request(op,belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(op->pending_server_trans)),status);
if (contact) belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),BELLE_SIP_HEADER(contact));
belle_sip_server_transaction_send_response(op->pending_server_trans,response);
return 0;
}
int sal_call_update(SalOp *op, const char *subject){
belle_sip_request_t *update;
belle_sip_dialog_state_t state=belle_sip_dialog_get_state(op->dialog);
/*check for dialog state*/
if ( state == BELLE_SIP_DIALOG_CONFIRMED) {
update=belle_sip_dialog_create_request(op->dialog,"INVITE");
} else if (state == BELLE_SIP_DIALOG_EARLY) {
update=belle_sip_dialog_create_request(op->dialog,"UPDATE");
} else {
ms_error("Cannot update op [%p] with dialog [%p] in state [%s]",op, op->dialog,belle_sip_dialog_state_to_string(state));
return -1;
}
if (update){
belle_sip_message_add_header(BELLE_SIP_MESSAGE(update),belle_sip_header_create( "Subject", subject));
sal_op_fill_invite(op, update);
return sal_op_send_request(op,update);
}
return -1;
}
SalMediaDescription * sal_call_get_remote_media_description(SalOp *h){
return h->base.remote_media;;
}
SalMediaDescription * sal_call_get_final_media_description(SalOp *h){
if (h->base.local_media && h->base.remote_media && !h->result){
sdp_process(h);
}
return h->result;
}
int sal_call_send_dtmf(SalOp *h, char dtmf){
if (h->dialog && (belle_sip_dialog_get_state(h->dialog) == BELLE_SIP_DIALOG_CONFIRMED || belle_sip_dialog_get_state(h->dialog) == BELLE_SIP_DIALOG_EARLY)){
belle_sip_request_t *req=belle_sip_dialog_create_queued_request(h->dialog,"INFO");
if (req){
int bodylen;
char dtmf_body[128]={0};
snprintf(dtmf_body, sizeof(dtmf_body)-1, "Signal=%c\r\nDuration=250\r\n", dtmf);
bodylen=strlen(dtmf_body);
belle_sip_message_set_body((belle_sip_message_t*)req,dtmf_body,bodylen);
belle_sip_message_add_header((belle_sip_message_t*)req,(belle_sip_header_t*)belle_sip_header_content_length_create(bodylen));
belle_sip_message_add_header((belle_sip_message_t*)req,(belle_sip_header_t*)belle_sip_header_content_type_create("application", "dtmf-relay"));
sal_op_send_request(h,req);
}else ms_error("sal_call_send_dtmf(): could not build request");
}else ms_error("sal_call_send_dtmf(): no dialog");
return 0;
}
int sal_call_terminate(SalOp *op){
belle_sip_dialog_state_t dialog_state=op->dialog?belle_sip_dialog_get_state(op->dialog):BELLE_SIP_DIALOG_NULL;
if (op->state==SalOpStateTerminating || op->state==SalOpStateTerminated) {
ms_error("Cannot terminate op [%p] in state [%s]",op,sal_op_state_to_string(op->state));
return -1;
}
switch(dialog_state) {
case BELLE_SIP_DIALOG_CONFIRMED: {
sal_op_send_request(op,belle_sip_dialog_create_request(op->dialog,"BYE"));
op->state=SalOpStateTerminating;
break;
}
case BELLE_SIP_DIALOG_NULL: {
if (op->dir == SalOpDirIncoming) {
sal_call_decline(op, SalReasonDeclined,NULL);
op->state=SalOpStateTerminated;
} else if (op->pending_client_trans){
if (belle_sip_transaction_get_state(BELLE_SIP_TRANSACTION(op->pending_client_trans)) == BELLE_SIP_TRANSACTION_PROCEEDING){
cancelling_invite(op);
}else{
/* Case where the CANCEL cannot be sent because no provisional response was received so far.
* The Op must be kept for the time of the transaction in case a response is received later.
* The state is passed to Terminating to remember to terminate later.
*/
op->state=SalOpStateTerminating;
}
}
break;
}
case BELLE_SIP_DIALOG_EARLY: {
if (op->dir == SalOpDirIncoming) {
sal_call_decline(op, SalReasonDeclined,NULL);
op->state=SalOpStateTerminated;
} else {
cancelling_invite(op);
}
break;
}
default: {
ms_error("sal_call_terminate not implemented yet for dialog state [%s]",belle_sip_dialog_state_to_string(dialog_state));
return -1;
}
}
return 0;
}
bool_t sal_call_autoanswer_asked(SalOp *op){
return op->auto_answer_asked;
}
void sal_call_send_vfu_request(SalOp *op){
char info_body[] =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
"<media_control>"
" <vc_primitive>"
" <to_encoder>"
" <picture_fast_update></picture_fast_update>"
" </to_encoder>"
" </vc_primitive>"
"</media_control>";
size_t content_lenth = sizeof(info_body) - 1;
belle_sip_dialog_state_t dialog_state=op->dialog?belle_sip_dialog_get_state(op->dialog):BELLE_SIP_DIALOG_NULL; /*no dialog = dialog in NULL state*/
if (dialog_state == BELLE_SIP_DIALOG_CONFIRMED) {
belle_sip_request_t* info = belle_sip_dialog_create_queued_request(op->dialog,"INFO");
int error=TRUE;
if (info) {
belle_sip_message_add_header(BELLE_SIP_MESSAGE(info),BELLE_SIP_HEADER(belle_sip_header_content_type_create("application","media_control+xml")));
belle_sip_message_add_header(BELLE_SIP_MESSAGE(info),BELLE_SIP_HEADER(belle_sip_header_content_length_create(content_lenth)));
belle_sip_message_set_body(BELLE_SIP_MESSAGE(info),info_body,content_lenth);
error=sal_op_send_request(op,info);
}
if (error)
ms_warning("Cannot send vfu request to [%s] ", sal_op_get_to(op));
} else {
ms_warning("Cannot send vfu request to [%s] because dialog [%p] in wrong state [%s]",sal_op_get_to(op)
,op->dialog
,belle_sip_dialog_state_to_string(dialog_state));
}
return ;
}
int sal_call_is_offerer(const SalOp *h){
return h->sdp_offering;
}