linphone-iphone/coreapi/sal/call_op.cpp
2017-09-29 17:19:52 +02:00

1475 lines
58 KiB
C++

#include "call_op.h"
#include "bellesip_sal/sal_impl.h"
#include "offeranswer.h"
#include <bctoolbox/defs.h>
#include <belle-sip/provider.h>
using namespace std;
#define SIP_MESSAGE_BODY_LIMIT 16*1024 // 16kB
LINPHONE_BEGIN_NAMESPACE
int SalCallOp::set_local_media_description(SalMediaDescription *desc) {
if (this->custom_body) {
bctbx_error("cannot set local media description on SalOp [%p] because a custom body is already set", this);
return -1;
}
if (desc) sal_media_description_ref(desc);
if (this->local_media) sal_media_description_unref(this->local_media);
this->local_media=desc;
if (this->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 (this->sdp_answer){
belle_sip_object_unref(this->sdp_answer);
this->sdp_answer=NULL;
}
}
return 0;
}
int SalCallOp::set_local_custom_body(SalCustomBody *body) {
if (this->local_media) {
bctbx_error("cannot set custom body on SalOp [%p] because a local media description is already set", this);
return -1;
}
if (this->custom_body) sal_custom_body_unref(this->custom_body);
this->custom_body = sal_custom_body_ref(body ? body : NULL);
return 0;
}
belle_sip_header_allow_t *SalCallOp::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;
}
int SalCallOp::set_custom_body(belle_sip_message_t *msg, const SalCustomBody *body) {
if (body->data_length > SIP_MESSAGE_BODY_LIMIT) {
bctbx_error("trying to add a body greater than %dkB to message [%p]", SIP_MESSAGE_BODY_LIMIT/1024, msg);
return -1;
}
if (body->data_length == 0 || body->raw_data == NULL) {
bctbx_error("trying to add an empty custom body to message [%p]", msg);
return -1;
}
belle_sip_header_content_type_t *content_type = belle_sip_header_content_type_create(body->type->type, body->type->subtype);
belle_sip_header_content_length_t *content_length = belle_sip_header_content_length_create(body->data_length);
belle_sip_message_add_header(msg, BELLE_SIP_HEADER(content_type));
belle_sip_message_add_header(msg, BELLE_SIP_HEADER(content_length));
char *buffer = bctbx_new(char, body->data_length);
memcpy(buffer, body->raw_data, body->data_length);
belle_sip_message_assign_body(msg, buffer, body->data_length);
return 0;
}
int SalCallOp::set_sdp(belle_sip_message_t *msg,belle_sdp_session_description_t* session_desc) {
belle_sip_error_code error = BELLE_SIP_BUFFER_OVERFLOW;
size_t length = 0;
if (session_desc == NULL) return -1;
size_t bufLen = 2048;
char *buff = reinterpret_cast<char *>(belle_sip_malloc(bufLen));
/* try to marshal the description. This could go higher than 2k so we iterate */
while( error != BELLE_SIP_OK && bufLen <= SIP_MESSAGE_BODY_LIMIT && buff != NULL){
error = belle_sip_object_marshal(BELLE_SIP_OBJECT(session_desc),buff,bufLen,&length);
if( error != BELLE_SIP_OK ){
bufLen *= 2;
length = 0;
buff = reinterpret_cast<char *>(belle_sip_realloc(buff,bufLen));
}
}
/* give up if hard limit reached */
if (error != BELLE_SIP_OK || buff == NULL) {
ms_error("Buffer too small (%d) or not enough memory, giving up SDP", (int)bufLen);
return -1;
}
SalMimeType *mimetype = sal_mime_type_new("application", "sdp");
SalCustomBody *body = sal_custom_body_new_with_buffer_moving(mimetype, buff, length);
set_custom_body(msg, body);
sal_custom_body_unref(body);
return 0;
}
int SalCallOp::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;
}
void SalCallOp::fill_invite(belle_sip_request_t* invite) {
belle_sip_message_add_header(BELLE_SIP_MESSAGE(invite),BELLE_SIP_HEADER(create_allow(this->root->enable_sip_update)));
if (this->root->session_expires!=0){
belle_sip_message_add_header(BELLE_SIP_MESSAGE(invite),belle_sip_header_create( "Session-expires", "600;refresher=uas"));
belle_sip_message_add_header(BELLE_SIP_MESSAGE(invite),belle_sip_header_create( "Supported", "timer"));
}
if (this->local_media){
this->sdp_offering=TRUE;
set_sdp_from_desc(BELLE_SIP_MESSAGE(invite),this->local_media);
} else this->sdp_offering=FALSE;
if (this->custom_body) {
set_custom_body(BELLE_SIP_MESSAGE(invite), this->custom_body);
}
}
void SalCallOp::set_released() {
if (!this->call_released){
this->state=State::Terminated;
this->root->callbacks.call_released(this);
this->call_released=TRUE;
/*be aware that the following line may destroy the op*/
set_or_update_dialog(NULL);
}
}
void SalCallOp::process_io_error_cb(void *user_ctx, const belle_sip_io_error_event_t *event) {
SalCallOp *op = (SalCallOp *)user_ctx;
if (op->state == State::Terminated) return;
if (op->pending_client_trans && (belle_sip_transaction_get_state(BELLE_SIP_TRANSACTION(op->pending_client_trans)) == BELLE_SIP_TRANSACTION_INIT)) {
sal_error_info_set(&op->error_info, SalReasonIOError, "SIP", 503, "IO error", NULL);
op->root->callbacks.call_failure(op);
if (!op->dialog || belle_sip_dialog_get_state(op->dialog) != BELLE_SIP_DIALOG_CONFIRMED){
/* Call terminated very very early, before INVITE is even sent, probably DNS resolution timeout. */
op->state = State::Terminating;
op->set_released();
}
} else {
/* Nothing to be done. If the error comes from a connectivity loss,
* the call will be marked as broken, and an attempt to repair it will be done. */
}
}
void SalCallOp::cancelling_invite(const SalErrorInfo *info) {
cancel_invite_with_info(info);
this->state=State::Terminating;
}
SalCustomBody *SalCallOp::extract_body(belle_sip_message_t *message) {
const char *body_str = belle_sip_message_get_body(message);
belle_sip_header_content_type_t *content_type = belle_sip_message_get_header_by_type(message, belle_sip_header_content_type_t);
belle_sip_header_content_length_t *content_length = belle_sip_message_get_header_by_type(message, belle_sip_header_content_length_t);
if (!(body_str && content_type && content_length)) return NULL;
const char *type_str = belle_sip_header_content_type_get_type(content_type);
const char *subtype_str = belle_sip_header_content_type_get_subtype(content_type);
size_t length = belle_sip_header_content_length_get_content_length(content_length);
SalMimeType *mime_type = sal_mime_type_new(type_str, subtype_str);
SalCustomBody *body = sal_custom_body_new_with_buffer_copy(mime_type, body_str, length);
return body;
}
int SalCallOp::extract_sdp(belle_sip_message_t* message,belle_sdp_session_description_t** session_desc, SalReason *error) {
*session_desc = NULL;
*error = SalReasonNone;
if (this->sdp_handling == SalOpSDPSimulateError) {
ms_error("Simulating SDP parsing error for op %p", this);
*error = SalReasonNotAcceptable;
return -1;
}
if (this->sdp_handling == SalOpSDPSimulateRemove) {
ms_error("Simulating no SDP for op %p", this);
return 0;
}
SalCustomBody *body = extract_body(message);
if (body == NULL) return 0;
if (strcmp("application", body->type->type) != 0 || strcmp("sdp", body->type->subtype) != 0) {
sal_custom_body_unref(body);
*error = SalReasonUnsupportedContent;
return -1;
}
*session_desc = belle_sdp_session_description_parse(string(body->raw_data, body->data_length).c_str());
sal_custom_body_unref(body);
if (*session_desc == NULL) {
ms_error("Failed to parse SDP message.");
*error = SalReasonNotAcceptable;
return -1;
}
return 0;
}
void SalCallOp::set_addr_to_0000(char value[], size_t sz) {
if (ms_is_ipv6(value)) {
strncpy(value,"::0", sz);
} else {
strncpy(value,"0.0.0.0", sz);
}
return;
}
void SalCallOp::sdp_process(){
ms_message("Doing SDP offer/answer process of type %s", this->sdp_offering ? "outgoing" : "incoming");
if (this->result){
sal_media_description_unref(this->result);
this->result = NULL;
}
/* if SDP was invalid */
if (this->remote_media == NULL) return;
this->result=sal_media_description_new();
if (this->sdp_offering){
offer_answer_initiate_outgoing(this->root->factory, this->local_media,this->remote_media,this->result);
}else{
int i;
if (this->sdp_answer){
belle_sip_object_unref(this->sdp_answer);
}
offer_answer_initiate_incoming(this->root->factory, this->local_media,this->remote_media,this->result,this->root->one_matching_codec);
/*for backward compatibility purpose*/
if(this->_cnx_ip_to_0000_if_sendonly_enabled && sal_media_description_has_dir(this->result,SalStreamSendOnly)) {
set_addr_to_0000(this->result->addr, sizeof(this->result->addr));
for(i=0;i<SAL_MEDIA_DESCRIPTION_MAX_STREAMS;++i){
if (this->result->streams[i].dir == SalStreamSendOnly) {
set_addr_to_0000(this->result->streams[i].rtp_addr, sizeof(this->result->streams[i].rtp_addr));
set_addr_to_0000(this->result->streams[i].rtcp_addr, sizeof(this->result->streams[i].rtcp_addr));
}
}
}
this->sdp_answer=(belle_sdp_session_description_t *)belle_sip_object_ref(media_description_to_sdp(this->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(this->result->addr,this->remote_media->addr);
this->result->bandwidth=this->remote_media->bandwidth;
for(i=0;i<SAL_MEDIA_DESCRIPTION_MAX_STREAMS;++i){
/*copy back parameters from remote description that we need in our result description*/
if (this->result->streams[i].rtp_port!=0){ /*if stream was accepted*/
strcpy(this->result->streams[i].rtp_addr,this->remote_media->streams[i].rtp_addr);
this->result->streams[i].ptime=this->remote_media->streams[i].ptime;
this->result->streams[i].bandwidth=this->remote_media->streams[i].bandwidth;
this->result->streams[i].rtp_port=this->remote_media->streams[i].rtp_port;
strcpy(this->result->streams[i].rtcp_addr,this->remote_media->streams[i].rtcp_addr);
this->result->streams[i].rtcp_port=this->remote_media->streams[i].rtcp_port;
if (sal_stream_description_has_srtp(&this->result->streams[i])) {
this->result->streams[i].crypto[0] = this->remote_media->streams[i].crypto[0];
}
}
}
}
}
void SalCallOp::handle_sdp_from_response(belle_sip_response_t* response) {
belle_sdp_session_description_t* sdp;
SalReason reason;
if (this->remote_media){
sal_media_description_unref(this->remote_media);
this->remote_media=NULL;
}
if (extract_sdp(BELLE_SIP_MESSAGE(response),&sdp,&reason)==0) {
if (sdp){
this->remote_media=sal_media_description_new();
sdp_to_media_description(sdp,this->remote_media);
}/*if no sdp in response, what can we do ?*/
}
/* process sdp in any case to reset result media description*/
if (this->local_media) sdp_process();
}
void SalCallOp::set_error(belle_sip_response_t* response, bool_t fatal){
set_error_info_from_response(response);
if (fatal) this->state = State::Terminating;
this->root->callbacks.call_failure(this);
}
int SalCallOp::vfu_retry_cb (void *user_data, unsigned int events) {
SalCallOp *op=(SalCallOp *)user_data;
op->send_vfu_request();
op->ref();
return BELLE_SIP_STOP;
}
void SalCallOp::process_response_cb(void *op_base, const belle_sip_response_event_t *event) {
SalCallOp * op = (SalCallOp *)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);
const char *method;
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));
op->set_or_update_dialog(dialog);
dialog_state=dialog ? belle_sip_dialog_get_state(dialog) : BELLE_SIP_DIALOG_NULL;
method=belle_sip_request_get_method(req);
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));
/*to make sure no cb will destroy op*/
op->ref();
switch(dialog_state) {
case BELLE_SIP_DIALOG_NULL:
case BELLE_SIP_DIALOG_EARLY: {
if (strcmp("INVITE",method)==0 ) {
if (op->state == State::Terminating) {
/*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) {
op->cancelling_invite(NULL);
}else{
/* no need to send the INVITE because the UAS rejected the INVITE*/
if (op->dialog==NULL) op->set_released();
}
} 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) op->set_released();
}
}
} else if (code >= 180 && code<200) {
belle_sip_response_t *prev_response=reinterpret_cast<belle_sip_response_t *>(belle_sip_object_data_get(BELLE_SIP_OBJECT(dialog),"early_response"));
if (!prev_response || code>belle_sip_response_get_status_code(prev_response)){
op->handle_sdp_from_response(response);
op->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){
op->set_error(response, TRUE);
if (op->dialog==NULL) op->set_released();
}
} else if (code >=200 && code<300) {
if (strcmp("UPDATE",method)==0) {
op->handle_sdp_from_response(response);
op->root->callbacks.call_accepted(op);
} else if (strcmp("CANCEL", method) == 0) {
op->root->callbacks.call_cancel_done(op);
}
}
}
break;
case BELLE_SIP_DIALOG_CONFIRMED: {
switch (op->state) {
case State::Early:/*invite case*/
case State::Active: /*re-invite, INFO, UPDATE case*/
if (strcmp("INVITE",method)==0){
if (code >=200 && code<300) {
op->handle_sdp_from_response(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_message_add_header(BELLE_SIP_MESSAGE(ack),BELLE_SIP_HEADER(op->root->user_agent));
op->root->callbacks.call_accepted(op); /*INVITE*/
op->root->callbacks.call_ack_being_sent(op, (SalCustomHeader*)ack);
belle_sip_dialog_send_ack(op->dialog,ack);
op->state=State::Active;
}else if (code >= 300){
op->set_error(response, FALSE);
}
}else if (strcmp("INFO",method)==0){
if (code == 491
&& (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 = rand() % 1001; // [0;1000]
belle_sip_source_t *s=op->root->create_timer(vfu_retry_cb,op->ref(), 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*/
}
}else if (strcmp("UPDATE",method)==0){
op->root->callbacks.call_accepted(op); /*INVITE*/
}else if (strcmp("CANCEL",method)==0){
op->root->callbacks.call_cancel_done(op);
}
break;
case State::Terminating:
op->send_request(belle_sip_dialog_create_request(op->dialog,"BYE"));
break;
case State::Terminated:
default:
ms_error("Call op [%p] receives unexpected answer [%i] while in state [%s].",op,code, to_string(op->state));
}
}
break;
case BELLE_SIP_DIALOG_TERMINATED: {
if (strcmp("INVITE",method)==0 && code >= 300){
op->set_error(response, TRUE);
}
}
break;
default: {
ms_error("call op [%p] receive answer [%i] not implemented",op,code);
}
break;
}
op->unref();
}
void SalCallOp::process_timeout_cb(void *user_ctx, const belle_sip_timeout_event_t *event) {
SalCallOp * op=(SalCallOp *)user_ctx;
if (op->state==State::Terminated) return;
if (!op->dialog) {
/*call terminated very early*/
sal_error_info_set(&op->error_info, SalReasonRequestTimeout, "SIP", 408, "Request timeout", NULL);
op->root->callbacks.call_failure(op);
op->state = State::Terminating;
op->set_released();
} else {
/*dialog will terminated shortly, nothing to do*/
}
}
void SalCallOp::process_transaction_terminated_cb(void *user_ctx, const belle_sip_transaction_terminated_event_t *event) {
SalCallOp * op = (SalCallOp *)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;
int code = 0;
bool_t release_call=FALSE;
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 (resp) code = belle_sip_response_get_status_code(resp);
if (op->state == State::Terminating
&& 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))
&& op->dialog==NULL) {
release_call=TRUE;
}else if (op->state == State::Early && code < 200){
/*call terminated early*/
sal_error_info_set(&op->error_info, SalReasonIOError, "SIP", 503, "I/O error", NULL);
op->state = State::Terminating;
op->root->callbacks.call_failure(op);
release_call=TRUE;
}
if (server_transaction){
if (op->pending_server_trans==server_transaction){
belle_sip_object_unref(op->pending_server_trans);
op->pending_server_trans=NULL;
}
if (op->pending_update_server_trans==server_transaction){
belle_sip_object_unref(op->pending_update_server_trans);
op->pending_update_server_trans=NULL;
}
}
if (release_call) op->set_released();
}
int SalCallOp::is_media_description_acceptable(SalMediaDescription *md) {
if (md->nb_streams==0){
ms_warning("Media description does not define any stream.");
return FALSE;
}
return TRUE;
}
SalReason SalCallOp::process_sdp_for_invite(belle_sip_request_t* invite) {
belle_sdp_session_description_t* sdp;
SalReason reason = SalReasonNone;
SalErrorInfo sei;
memset(&sei, 0, sizeof(sei));
if (extract_sdp(BELLE_SIP_MESSAGE(invite),&sdp,&reason)==0) {
if (sdp){
this->sdp_offering=FALSE;
this->remote_media=sal_media_description_new();
sdp_to_media_description(sdp,this->remote_media);
/*make some sanity check about the SDP received*/
if (!is_media_description_acceptable(this->remote_media)){
reason=SalReasonNotAcceptable;
}
belle_sip_object_unref(sdp);
}else this->sdp_offering=TRUE; /*INVITE without SDP*/
}
if (reason != SalReasonNone){
sal_error_info_set(&sei, reason,"SIP", 0, NULL, NULL);
decline_with_error_info(&sei,NULL);
sal_error_info_reset(&sei);
}
return reason;
}
void SalCallOp::call_terminated(belle_sip_server_transaction_t* server_transaction, int status_code, belle_sip_request_t* cancel_request) {
belle_sip_response_t* resp;
belle_sip_request_t* server_req = belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(server_transaction));
this->state = State::Terminating;
set_reason_error_info(BELLE_SIP_MESSAGE(cancel_request ? cancel_request : server_req));
resp=create_response_from_request(server_req,status_code);
belle_sip_server_transaction_send_response(server_transaction,resp);
this->root->callbacks.call_terminated(this,this->dir==Dir::Incoming?this->get_from():this->get_to());
}
void SalCallOp::reset_descriptions() {
if (this->remote_media){
sal_media_description_unref(this->remote_media);
this->remote_media=NULL;
}
if (this->result){
sal_media_description_unref(this->result);
this->result=NULL;
}
}
void SalCallOp::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);
}
bool_t SalCallOp::is_a_pending_invite_incoming_transaction(belle_sip_transaction_t *tr){
return BELLE_SIP_OBJECT_IS_INSTANCE_OF(tr, belle_sip_ist_t) && belle_sip_transaction_state_is_transient(
belle_sip_transaction_get_state(tr));
}
void SalCallOp::process_request_event_cb(void *op_base, const belle_sip_request_event_t *event) {
SalCallOp * op = (SalCallOp *)op_base;
SalReason reason;
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);
bool_t is_update=FALSE;
bool_t drop_op = FALSE;
if (strcmp("ACK",method)!=0){ /*ACK doesn't create a server transaction*/
server_transaction = belle_sip_provider_create_server_transaction(op->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),op->ref());
}
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) {
op->set_or_update_dialog(belle_sip_provider_create_dialog(op->root->prov, BELLE_SIP_TRANSACTION(op->pending_server_trans)));
ms_message("new incoming call from [%s] to [%s]",op->get_from(),op->get_to());
}
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");
}
if ( (reason = op->process_sdp_for_invite(req)) == SalReasonNone) {
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->root->callbacks.call_received(op);
}else{
sal_error_info_set(&op->error_info, reason, "SIP", 0, NULL, NULL);
op->root->callbacks.call_rejected(op);
/*the INVITE was declined by process_sdp_for_invite(). As we are not inside an established dialog, we can drop the op immediately*/
drop_op = TRUE;
}
break;
}BCTBX_NO_BREAK; /* else same behavior as for EARLY state, thus NO BREAK*/
}
case BELLE_SIP_DIALOG_EARLY: {
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
,op->create_response_from_request(req,200));
/*terminate invite transaction*/
op->call_terminated(op->pending_server_trans,487,req);
} else {
/*call leg does not exist*/
belle_sip_server_transaction_send_response(server_transaction
,op->create_response_from_request(req,481));
}
} else if (strcmp("PRACK",method)==0) {
resp=op->create_response_from_request(req,200);
belle_sip_server_transaction_send_response(server_transaction,resp);
} else if (strcmp("UPDATE",method)==0) {
op->reset_descriptions();
if (op->process_sdp_for_invite(req)==SalReasonNone)
op->root->callbacks.call_updating(op,TRUE);
} 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->pending_client_trans ||
!belle_sip_transaction_state_is_transient(belle_sip_transaction_get_state((belle_sip_transaction_t*)op->pending_client_trans))){
if (op->sdp_offering){
SalReason reason;
if (op->extract_sdp(BELLE_SIP_MESSAGE(req),&sdp,&reason)==0) {
if (sdp){
if (op->remote_media)
sal_media_description_unref(op->remote_media);
op->remote_media=sal_media_description_new();
sdp_to_media_description(sdp,op->remote_media);
op->sdp_process();
belle_sip_object_unref(sdp);
}else{
ms_warning("SDP expected in ACK but not found.");
}
}
}
op->root->callbacks.call_ack_received(op, (SalCustomHeader*)req);
}else{
ms_message("Ignored received ack since a new client transaction has been started since.");
}
} else if(strcmp("BYE",method)==0) {
op->call_terminated(server_transaction,200,req);
/*call end not notified by dialog deletion because transaction can end before dialog*/
} else if(strcmp("INVITE",method)==0 || (is_update=(strcmp("UPDATE",method)==0)) ) {
if (is_update && !belle_sip_message_get_body(BELLE_SIP_MESSAGE(req))) {
/*session timer case*/
/*session expire should be handled. to be done when real session timer (rfc4028) will be implemented*/
resp=op->create_response_from_request(req,200);
belle_sip_server_transaction_send_response(server_transaction,resp);
belle_sip_object_unref(op->pending_update_server_trans);
op->pending_update_server_trans=NULL;
} else {
/*re-invite*/
op->reset_descriptions();
if (op->process_sdp_for_invite(req)==SalReasonNone)
op->root->callbacks.call_updating(op,is_update);
}
} 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->root->callbacks.vfu_request){
op->root->callbacks.vfu_request(op);
}
}else{
belle_sip_message_t *msg = BELLE_SIP_MESSAGE(req);
belle_sip_body_handler_t *body_handler = BELLE_SIP_BODY_HANDLER(op->get_body_handler(msg));
if (body_handler) {
belle_sip_header_content_type_t *content_type = belle_sip_message_get_header_by_type(msg, belle_sip_header_content_type_t);
if (content_type
&& (strcmp(belle_sip_header_content_type_get_type(content_type), "application") == 0)
&& (strcmp(belle_sip_header_content_type_get_subtype(content_type), "dtmf-relay") == 0)) {
char tmp[10];
if (sal_lines_get_value(belle_sip_message_get_body(msg), "Signal",tmp, sizeof(tmp))){
op->root->callbacks.dtmf_received(op,tmp[0]);
}
}else
op->root->callbacks.info_received(op, (SalBodyHandler *)body_handler);
} else {
op->root->callbacks.info_received(op,NULL);
}
}
resp=op->create_response_from_request(req,200);
belle_sip_server_transaction_send_response(server_transaction,resp);
}else if (strcmp("REFER",method)==0) {
op->process_refer(event,server_transaction);
} else if (strcmp("NOTIFY",method)==0) {
op->process_notify(event,server_transaction);
} else if (strcmp("OPTIONS",method)==0) {
resp=op->create_response_from_request(req,200);
belle_sip_server_transaction_send_response(server_transaction,resp);
} else if (strcmp("CANCEL",method)==0) {
belle_sip_transaction_t *last_transaction = belle_sip_dialog_get_last_transaction(op->dialog);
if (last_transaction == NULL || !is_a_pending_invite_incoming_transaction(last_transaction) ) {
/*call leg does not exist because 200ok already sent*/
belle_sip_server_transaction_send_response(server_transaction,op->create_response_from_request(req,481));
} else {
/* CANCEL on re-INVITE for which a 200ok has not been sent yet */
belle_sip_server_transaction_send_response(server_transaction, op->create_response_from_request(req, 200));
belle_sip_server_transaction_send_response(BELLE_SIP_SERVER_TRANSACTION(last_transaction),
op->create_response_from_request(belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(last_transaction)), 487));
}
} else if (strcmp("MESSAGE",method)==0){
op->process_incoming_message(event);
}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);
if (drop_op) op->release();
}
void SalCallOp::set_call_as_released(SalCallOp *op) {
op->set_released();
}
void SalCallOp::process_dialog_terminated_cb(void *ctx, const belle_sip_dialog_terminated_event_t *event) {
SalCallOp * op=(SalCallOp *)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_EARLY:
case BELLE_SIP_DIALOG_NULL:
if (op->state!=State::Terminated && op->state!=State::Terminating) {
/*this is an early termination due to incorrect response received*/
op->root->callbacks.call_failure(op);
op->state=State::Terminating;
}
break;
case BELLE_SIP_DIALOG_CONFIRMED:
if (op->state!=State::Terminated && op->state!=State::Terminating) {
/*this is probably a normal termination from a BYE*/
op->root->callbacks.call_terminated(op,op->dir==Dir::Incoming?op->get_from():op->get_to());
op->state=State::Terminating;
}
break;
default:
break;
}
belle_sip_main_loop_do_later(belle_sip_stack_get_main_loop(op->root->stack)
,(belle_sip_callback_t) set_call_as_released
, op);
} else {
ms_error("dialog unknown for op ");
}
}
void SalCallOp::fill_cbs() {
static belle_sip_listener_callbacks_t call_op_callbacks = {0};
if (call_op_callbacks.process_response_event==NULL){
call_op_callbacks.process_io_error=process_io_error_cb;
call_op_callbacks.process_response_event=process_response_cb;
call_op_callbacks.process_timeout=process_timeout_cb;
call_op_callbacks.process_transaction_terminated=process_transaction_terminated_cb;
call_op_callbacks.process_request_event=process_request_event_cb;
call_op_callbacks.process_dialog_terminated=process_dialog_terminated_cb;
}
this->callbacks=&call_op_callbacks;
this->type=Type::Call;
}
int SalCallOp::call(const char *from, const char *to, const char *subject) {
belle_sip_request_t* invite;
this->dir=Dir::Outgoing;
set_from(from);
set_to(to);
ms_message("[%s] calling [%s] on op [%p]", from, to, this);
invite=build_request("INVITE");
if( invite == NULL ){
/* can happen if the op has an invalid address */
return -1;
}
fill_invite(invite);
if (subject) belle_sip_message_add_header(BELLE_SIP_MESSAGE(invite), belle_sip_header_create("Subject", subject));
fill_cbs();
if (this->replaces){
belle_sip_message_add_header(BELLE_SIP_MESSAGE(invite),BELLE_SIP_HEADER(this->replaces));
}
if (this->referred_by)
belle_sip_message_add_header(BELLE_SIP_MESSAGE(invite),BELLE_SIP_HEADER(this->referred_by));
return send_request(invite);
}
int SalCallOp::notify_ringing(bool_t early_media){
int status_code =early_media?183:180;
belle_sip_request_t* req=belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(this->pending_server_trans));
belle_sip_response_t* ringing_response = create_response_from_request(req,status_code);
belle_sip_header_t *require;
const char *tags=NULL;
if (early_media){
handle_offer_answer_response(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*)get_contact_address();
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(this->pending_server_trans,ringing_response);
return 0;
}
int SalCallOp::accept() {
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 (this->pending_update_server_trans) {
transaction= this->pending_update_server_trans;
} else if (this->pending_server_trans) {
/*so it must be an invite/re-invite*/
transaction= this->pending_server_trans;
} else {
ms_error("No transaction to accept for op [%p]", this);
return -1;
}
ms_message("Accepting server transaction [%p] on op [%p]", transaction, this);
/* sends a 200 OK */
response = create_response_from_request(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(this->root->enable_sip_update)));
if (this->root->session_expires!=0){
/* if (h->supports_session_timers) {*/
belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),belle_sip_header_create("Supported", "timer"));
belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),belle_sip_header_create( "Session-expires", "600;refresher=uac"));
/*}*/
}
if ((contact_header=create_contact())) {
belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),BELLE_SIP_HEADER(contact_header));
}
add_custom_headers(BELLE_SIP_MESSAGE(response));
handle_offer_answer_response(response);
belle_sip_server_transaction_send_response(transaction,response);
if (this->pending_update_server_trans) {
belle_sip_object_unref(this->pending_update_server_trans);
this->pending_update_server_trans=NULL;
}
if (this->state == State::Early){
this->state = State::Active;
}
return 0;
}
int SalCallOp::decline(SalReason reason, const char *redirection /*optional*/){
belle_sip_response_t* response;
belle_sip_header_contact_t* contact=NULL;
int status=to_sip_code(reason);
belle_sip_transaction_t *trans;
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");
}
}
trans=(belle_sip_transaction_t*)this->pending_server_trans;
if (!trans) trans=(belle_sip_transaction_t*)this->pending_update_server_trans;
if (!trans){
ms_error("sal_call_decline(): no pending transaction to decline.");
return -1;
}
response = create_response_from_request(belle_sip_transaction_get_request(trans),status);
if (contact) belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),BELLE_SIP_HEADER(contact));
belle_sip_server_transaction_send_response(BELLE_SIP_SERVER_TRANSACTION(trans),response);
return 0;
}
belle_sip_header_reason_t *SalCallOp::make_reason_header( const SalErrorInfo *info){
if (info != NULL){
belle_sip_header_reason_t* reason = BELLE_SIP_HEADER_REASON(belle_sip_header_reason_new());
belle_sip_header_reason_set_text(reason, info->status_string);
belle_sip_header_reason_set_protocol(reason,info->protocol);
belle_sip_header_reason_set_cause(reason,info->protocol_code);
return reason;
}
return NULL;
}
int SalCallOp::decline_with_error_info(const SalErrorInfo *info, const char *redirection /*optional*/){
belle_sip_response_t* response;
belle_sip_header_contact_t* contact=NULL;
int status = info->protocol_code;
belle_sip_transaction_t *trans;
if (info->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");
}
}
trans=(belle_sip_transaction_t*)this->pending_server_trans;
if (!trans) trans=(belle_sip_transaction_t*)this->pending_update_server_trans;
if (!trans){
ms_error("sal_call_decline_with_error_info(): no pending transaction to decline.");
return -1;
}
response = create_response_from_request(belle_sip_transaction_get_request(trans),status);
belle_sip_header_reason_t* reason_header = make_reason_header(info->sub_sei);
if (reason_header) {
belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),BELLE_SIP_HEADER(reason_header));
}
if (contact) {
belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),BELLE_SIP_HEADER(contact));
}
belle_sip_server_transaction_send_response(BELLE_SIP_SERVER_TRANSACTION(trans),response);
return 0;
}
int SalCallOp::update(const char *subject, bool_t no_user_consent) {
belle_sip_request_t *update;
belle_sip_dialog_state_t state;
if (this->dialog == NULL) {
/* If the dialog does not exist, this is that we are trying to recover from a connection loss
during a very early state of outgoing call initiation (the dialog has not been created yet). */
const char *from = get_from();
const char *to = get_to();
return call(from, to, subject);
}
state = belle_sip_dialog_get_state(this->dialog);
belle_sip_dialog_enable_pending_trans_checking(this->dialog,this->root->pending_trans_checking);
/*check for dialog state*/
if ( state == BELLE_SIP_DIALOG_CONFIRMED) {
if (no_user_consent)
update=belle_sip_dialog_create_request(this->dialog,"UPDATE");
else
update=belle_sip_dialog_create_request(this->dialog,"INVITE");
} else if (state == BELLE_SIP_DIALOG_EARLY) {
update=belle_sip_dialog_create_request(this->dialog,"UPDATE");
} else {
ms_error("Cannot update op [%p] with dialog [%p] in state [%s]", this, this->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));
fill_invite(update);
return send_request(update);
}
/*it failed why ?*/
if (belle_sip_dialog_request_pending(this->dialog))
sal_error_info_set(&this->error_info,SalReasonRequestPending, "SIP", 491,NULL,NULL);
else
sal_error_info_set(&this->error_info,SalReasonUnknown, "SIP", 500,NULL,NULL);
return -1;
}
void SalCallOp::cancel_invite_with_info(const SalErrorInfo *info) {
belle_sip_request_t* cancel;
ms_message("Cancelling INVITE request from [%s] to [%s] ",get_from(), get_to());
cancel = belle_sip_client_transaction_create_cancel(this->pending_client_trans);
if (cancel){
if (info != NULL){
belle_sip_header_reason_t* reason = make_reason_header(info);
belle_sip_message_add_header(BELLE_SIP_MESSAGE(cancel),BELLE_SIP_HEADER(reason));
}
send_request(cancel);
}else if (this->dialog){
belle_sip_dialog_state_t state = belle_sip_dialog_get_state(this->dialog);;
/*case where the response received is invalid (could not establish a dialog), but the transaction is not cancellable
* because already terminated*/
switch(state){
case BELLE_SIP_DIALOG_EARLY:
case BELLE_SIP_DIALOG_NULL:
/*force kill the dialog*/
ms_warning("op [%p]: force kill of dialog [%p]", this, this->dialog);
belle_sip_dialog_delete(this->dialog);
break;
default:
break;
}
}
}
SalMediaDescription *SalCallOp::get_final_media_description() {
if (this->local_media && this->remote_media && !this->result){
sdp_process();
}
return this->result;
}
int SalCallOp::refer_to(belle_sip_header_refer_to_t* refer_to, belle_sip_header_referred_by_t* referred_by) {
char* tmp;
belle_sip_request_t* req=this->dialog?belle_sip_dialog_create_request(this->dialog,"REFER"):build_request("REFER");
if (!req) {
tmp=belle_sip_uri_to_string(belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(refer_to)));
ms_error("Cannot refer to [%s] for op [%p]",tmp, this);
belle_sip_free(tmp);
return -1;
}
belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),BELLE_SIP_HEADER(refer_to));
if (referred_by) belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),BELLE_SIP_HEADER(referred_by));
return send_request(req);
}
int SalCallOp::refer(const char *refer_to_){
belle_sip_header_address_t *referred_by;
belle_sip_header_refer_to_t* refer_to_header;
if (this->dialog) {
referred_by=(belle_sip_header_address_t*)belle_sip_object_clone(BELLE_SIP_OBJECT(belle_sip_dialog_get_local_party(this->dialog)));
}else{
referred_by=BELLE_SIP_HEADER_ADDRESS(get_from_address());
}
refer_to_header=belle_sip_header_refer_to_create(belle_sip_header_address_parse(refer_to_));
return refer_to(refer_to_header,belle_sip_header_referred_by_create(referred_by));
}
int SalCallOp::refer_with_replaces(SalCallOp *other_call_op) {
belle_sip_dialog_state_t other_call_dialog_state=other_call_op->dialog?belle_sip_dialog_get_state(other_call_op->dialog):BELLE_SIP_DIALOG_NULL;
belle_sip_dialog_state_t op_dialog_state= this->dialog?belle_sip_dialog_get_state(this->dialog):BELLE_SIP_DIALOG_NULL;
belle_sip_header_replaces_t* replaces;
belle_sip_header_refer_to_t* refer_to_;
belle_sip_header_referred_by_t* referred_by;
const char* from_tag;
const char* to_tag;
char* escaped_replaces;
/*first, build refer to*/
if ((other_call_dialog_state!=BELLE_SIP_DIALOG_CONFIRMED) && (other_call_dialog_state!=BELLE_SIP_DIALOG_EARLY)) {
ms_error("wrong dialog state [%s] for op [%p], should be BELLE_SIP_DIALOG_CONFIRMED or BELE_SIP_DIALOG_EARLY",
belle_sip_dialog_state_to_string(other_call_dialog_state),
other_call_op);
return -1;
}
if (op_dialog_state!=BELLE_SIP_DIALOG_CONFIRMED) {
ms_error("wrong dialog state [%s] for op [%p], should be BELLE_SIP_DIALOG_CONFIRMED",
belle_sip_dialog_state_to_string(op_dialog_state),
this);
return -1;
}
refer_to_ =belle_sip_header_refer_to_create(belle_sip_dialog_get_remote_party(other_call_op->dialog));
belle_sip_parameters_clean(BELLE_SIP_PARAMETERS(refer_to_));
/*rfc3891
...
4. User Agent Client Behavior: Sending a Replaces Header
A User Agent that wishes to replace a single existing early or
confirmed dialog with a new dialog of its own, MAY send the target
User Agent an INVITE request containing a Replaces header field. The
User Agent Client (UAC) places the Call-ID, to-tag, and from-tag
information for the target dialog in a single Replaces header field
and sends the new INVITE to the target.*/
from_tag=belle_sip_dialog_get_local_tag(other_call_op->dialog);
to_tag=belle_sip_dialog_get_remote_tag(other_call_op->dialog);
replaces=belle_sip_header_replaces_create(belle_sip_header_call_id_get_call_id(belle_sip_dialog_get_call_id(other_call_op->dialog))
,from_tag,to_tag);
escaped_replaces=belle_sip_header_replaces_value_to_escaped_string(replaces);
belle_sip_uri_set_header(belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(refer_to_)),"Replaces",escaped_replaces);
belle_sip_free(escaped_replaces);
referred_by=belle_sip_header_referred_by_create(belle_sip_dialog_get_local_party(this->dialog));
belle_sip_parameters_clean(BELLE_SIP_PARAMETERS(referred_by));
return refer_to(refer_to_,referred_by);
}
int SalCallOp::set_referer(SalCallOp *refered_call){
if (refered_call->replaces)
SalOp::set_replaces(refered_call->replaces);
if (refered_call->referred_by)
set_referred_by(refered_call->referred_by);
return 0;
}
SalCallOp *SalCallOp::get_replaces() {
if (this->replaces){
/*rfc3891
3. User Agent Server Behavior: Receiving a Replaces Header
The Replaces header contains information used to match an existing
SIP dialog (call-id, to-tag, and from-tag). Upon receiving an INVITE
with a Replaces header, the User Agent (UA) attempts to match this
information with a confirmed or early dialog. The User Agent Server
(UAS) matches the to-tag and from-tag parameters as if they were tags
present in an incoming request. In other words, the to-tag parameter
is compared to the local tag, and the from-tag parameter is compared
to the remote tag.
*/
belle_sip_dialog_t* dialog=belle_sip_provider_find_dialog(this->root->prov
,belle_sip_header_replaces_get_call_id(this->replaces)
,belle_sip_header_replaces_get_to_tag(this->replaces)
,belle_sip_header_replaces_get_from_tag(this->replaces));
if (!dialog) {
/*for backward compatibility with liblinphone <= 3.10.2-243 */
dialog=belle_sip_provider_find_dialog(this->root->prov
,belle_sip_header_replaces_get_call_id(this->replaces)
,belle_sip_header_replaces_get_from_tag(this->replaces)
,belle_sip_header_replaces_get_to_tag(this->replaces));
}
if (dialog) {
return (SalCallOp*)belle_sip_dialog_get_application_data(dialog);
}
}
return NULL;
}
int SalCallOp::send_dtmf(char dtmf){
if (this->dialog && (belle_sip_dialog_get_state(this->dialog) == BELLE_SIP_DIALOG_CONFIRMED || belle_sip_dialog_get_state(this->dialog) == BELLE_SIP_DIALOG_EARLY)){
belle_sip_request_t *req=belle_sip_dialog_create_queued_request(this->dialog,"INFO");
if (req){
size_t 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"));
send_request(req);
}else ms_error("sal_call_send_dtmf(): could not build request");
}else ms_error("sal_call_send_dtmf(): no dialog");
return 0;
}
int SalCallOp::terminate_with_error(const SalErrorInfo *info) {
SalErrorInfo sei;
const SalErrorInfo *p_sei;
belle_sip_dialog_state_t dialog_state = this->dialog ? belle_sip_dialog_get_state(this->dialog) : BELLE_SIP_DIALOG_NULL;
int ret = 0;
memset(&sei, 0, sizeof(sei));
if (info == NULL && dialog_state != BELLE_SIP_DIALOG_CONFIRMED && this->dir == Dir::Incoming){
/*the purpose of this line is to set a default SalErrorInfo for declining an incoming call (not yet established of course) */
sal_error_info_set(&sei,SalReasonDeclined, "SIP", 0, NULL, NULL);
p_sei = &sei;
} else{
p_sei = info;
}
if (this->state==State::Terminating || this->state==State::Terminated) {
ms_error("Cannot terminate op [%p] in state [%s]",this,to_string(this->state));
ret = -1;
goto end;
}
switch(dialog_state) {
case BELLE_SIP_DIALOG_CONFIRMED: {
belle_sip_request_t * req = belle_sip_dialog_create_request(this->dialog,"BYE");
if (info != NULL){
belle_sip_header_reason_t* reason = make_reason_header(info);
belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),BELLE_SIP_HEADER(reason));
}
send_request(req);
this->state=State::Terminating;
break;
}
case BELLE_SIP_DIALOG_NULL: {
if (this->dir == Dir::Incoming) {
decline_with_error_info(p_sei, NULL);
this->state=State::Terminated;
} else if (this->pending_client_trans){
if (belle_sip_transaction_get_state(BELLE_SIP_TRANSACTION(this->pending_client_trans)) == BELLE_SIP_TRANSACTION_PROCEEDING){
cancelling_invite(p_sei);
}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.
*/
this->state=State::Terminating;
/* However, even if the transaction is kept alive, we can stop sending retransmissions to avoid flowing the network with no longer
* necessary messages and avoid confusion in logs.*/
belle_sip_client_transaction_stop_retransmissions(this->pending_client_trans);
}
}
break;
}
case BELLE_SIP_DIALOG_EARLY: {
if (this->dir == Dir::Incoming) {
decline_with_error_info(p_sei,NULL);
this->state=State::Terminated;
} else {
cancelling_invite(p_sei);
}
break;
}
default: {
ms_error("sal_call_terminate not implemented yet for dialog state [%s]",belle_sip_dialog_state_to_string(dialog_state));
ret = -1;
goto end;
}
}
end:
sal_error_info_reset(&sei);
return ret;
}
void SalCallOp::send_vfu_request() {
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= this->dialog?belle_sip_dialog_get_state(this->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(this->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=send_request(info);
}
if (error)
ms_warning("Cannot send vfu request to [%s] ", get_to());
} else {
ms_warning("Cannot send vfu request to [%s] because dialog [%p] in wrong state [%s]",get_to()
,this->dialog
,belle_sip_dialog_state_to_string(dialog_state));
}
return ;
}
int SalCallOp::send_notify_for_refer(int code, const char *reason) {
belle_sip_request_t* notify=belle_sip_dialog_create_queued_request(this->dialog,"NOTIFY");
char *sipfrag=belle_sip_strdup_printf("SIP/2.0 %i %s\r\n",code,reason);
size_t content_length=strlen(sipfrag);
belle_sip_message_add_header(BELLE_SIP_MESSAGE(notify)
,BELLE_SIP_HEADER(belle_sip_header_subscription_state_create(BELLE_SIP_SUBSCRIPTION_STATE_ACTIVE,-1)));
belle_sip_message_add_header(BELLE_SIP_MESSAGE(notify),belle_sip_header_create("Event","refer"));
belle_sip_message_add_header(BELLE_SIP_MESSAGE(notify),BELLE_SIP_HEADER(belle_sip_header_content_type_create("message","sipfrag")));
belle_sip_message_add_header(BELLE_SIP_MESSAGE(notify),BELLE_SIP_HEADER(belle_sip_header_content_length_create(content_length)));
belle_sip_message_assign_body(BELLE_SIP_MESSAGE(notify),sipfrag,content_length);
return send_request(notify);
}
void SalCallOp::notify_last_response(SalCallOp *newcall) {
belle_sip_client_transaction_t *tr=newcall->pending_client_trans;
belle_sip_response_t *resp=NULL;
if (tr){
resp=belle_sip_transaction_get_response((belle_sip_transaction_t*)tr);
}
if (resp==NULL){
send_notify_for_refer(100, "Trying");
}else{
send_notify_for_refer(belle_sip_response_get_status_code(resp), belle_sip_response_get_reason_phrase(resp));
}
}
int SalCallOp::notify_refer_state(SalCallOp *newcall) {
belle_sip_dialog_state_t state;
if(belle_sip_dialog_get_state(this->dialog) == BELLE_SIP_DIALOG_TERMINATED){
return 0;
}
state = this->dialog?belle_sip_dialog_get_state(this->dialog):BELLE_SIP_DIALOG_NULL;
switch(state) {
case BELLE_SIP_DIALOG_EARLY:
send_notify_for_refer(100, "Trying");
break;
case BELLE_SIP_DIALOG_CONFIRMED:
send_notify_for_refer(200, "Ok");
break;
case BELLE_SIP_DIALOG_TERMINATED:
case BELLE_SIP_DIALOG_NULL:
notify_last_response(newcall);
break;
}
return 0;
}
void SalCallOp::set_replaces(const char *call_id, const char *from_tag, const char *to_tag) {
belle_sip_header_replaces_t *replaces = belle_sip_header_replaces_create(call_id, from_tag, to_tag);
SalOp::set_replaces(replaces);
}
void SalCallOp::set_sdp_handling(SalOpSDPHandling handling) {
if (handling != SalOpSDPNormal) ms_message("Enabling special SDP handling for SalOp[%p]!", this);
this->sdp_handling = handling;
}
void SalCallOp::process_refer(const belle_sip_request_event_t *event, belle_sip_server_transaction_t *server_transaction) {
belle_sip_request_t* req = belle_sip_request_event_get_request(event);
belle_sip_header_refer_to_t *refer_to= belle_sip_message_get_header_by_type(BELLE_SIP_MESSAGE(req),belle_sip_header_refer_to_t);
belle_sip_header_referred_by_t *referred_by= belle_sip_message_get_header_by_type(BELLE_SIP_MESSAGE(req),belle_sip_header_referred_by_t);
belle_sip_response_t* resp;
belle_sip_uri_t* refer_to_uri;
char* refer_to_uri_str;
ms_message("Receiving REFER request on op [%p]", this);
if (refer_to) {
refer_to_uri=belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(refer_to));
if (refer_to_uri && belle_sip_uri_get_header(refer_to_uri,"Replaces")) {
SalOp::set_replaces(belle_sip_header_replaces_create2(belle_sip_uri_get_header(refer_to_uri,"Replaces")));
belle_sip_uri_remove_header(refer_to_uri,"Replaces");
}
if (referred_by){
set_referred_by(referred_by);
}
refer_to_uri_str=belle_sip_uri_to_string(refer_to_uri);
resp = create_response_from_request(req,202);
belle_sip_server_transaction_send_response(server_transaction,resp);
this->root->callbacks.refer_received(this->root,this,refer_to_uri_str);
belle_sip_free(refer_to_uri_str);
} else {
ms_warning("cannot do anything with the refer without destination\n");
resp = create_response_from_request(req,400);
belle_sip_server_transaction_send_response(server_transaction,resp);
}
}
void SalCallOp::process_notify(const belle_sip_request_event_t *event, belle_sip_server_transaction_t* server_transaction) {
belle_sip_request_t* req = belle_sip_request_event_get_request(event);
const char* body = belle_sip_message_get_body(BELLE_SIP_MESSAGE(req));
belle_sip_header_t* header_event=belle_sip_message_get_header(BELLE_SIP_MESSAGE(req),"Event");
belle_sip_header_content_type_t* content_type = belle_sip_message_get_header_by_type(req,belle_sip_header_content_type_t);
belle_sip_response_t* resp;
ms_message("Receiving NOTIFY request on op [%p]", this);
if (header_event
&& strncasecmp(belle_sip_header_get_unparsed_value(header_event),"refer",strlen("refer"))==0
&& content_type
&& strcmp(belle_sip_header_content_type_get_type(content_type),"message")==0
&& strcmp(belle_sip_header_content_type_get_subtype(content_type),"sipfrag")==0
&& body){
belle_sip_response_t* sipfrag=BELLE_SIP_RESPONSE(belle_sip_message_parse(body));
if (sipfrag){
int code=belle_sip_response_get_status_code(sipfrag);
SalReferStatus status=SalReferFailed;
if (code<200){
status=SalReferTrying;
}else if (code<300){
status=SalReferSuccess;
}else if (code>=400){
status=SalReferFailed;
}
belle_sip_object_unref(sipfrag);
resp = create_response_from_request(req,200);
belle_sip_server_transaction_send_response(server_transaction,resp);
this->root->callbacks.notify_refer(this,status);
}
}else{
ms_error("Notify without sipfrag, trashing");
resp = create_response_from_request(req,501);
belle_sip_server_transaction_send_response(server_transaction,resp);
}
}
int SalCallOp::send_message(const char *from, const char *to, const char* content_type, const char *msg, const char *peer_uri) {
if (!this->dialog) return -1;
belle_sip_request_t* req=belle_sip_dialog_create_queued_request(this->dialog,"MESSAGE");
prepare_message_request(req, content_type, msg, peer_uri);
return send_request(req);
}
bool_t SalCallOp::compare_op(const SalCallOp *op2) const {
return (strcmp(this->call_id, op2->call_id) == 0);
}
void SalCallOp::handle_offer_answer_response(belle_sip_response_t* response) {
if (this->local_media){
/*this is the case where we received an invite without SDP*/
if (this->sdp_offering) {
set_sdp_from_desc(BELLE_SIP_MESSAGE(response),this->local_media);
}else{
if ( this->sdp_answer==NULL )
{
if( this->sdp_handling == SalOpSDPSimulateRemove ){
ms_warning("Simulating SDP removal in answer for op %p", this);
} else {
sdp_process();
}
}
if (this->sdp_answer){
set_sdp(BELLE_SIP_MESSAGE(response),this->sdp_answer);
belle_sip_object_unref(this->sdp_answer);
this->sdp_answer=NULL;
}
}
}else{
ms_error("You are accepting a call but not defined any media capabilities !");
}
}
LINPHONE_END_NAMESPACE