linphone-ios/coreapi/callbacks.c
François Grisez 274ff16b41 Fix double free
2017-04-05 10:18:25 +02:00

1515 lines
56 KiB
C

/*
linphone
Copyright (C) 2010 Simon MORLAT (simon.morlat@free.fr)
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "sal/sal.h"
#include "linphone/core.h"
#include "private.h"
#include "mediastreamer2/mediastream.h"
#include "linphone/lpconfig.h"
// stat
#ifndef _WIN32
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
static void register_failure(SalOp *op);
static int media_parameters_changed(LinphoneCall *call, SalMediaDescription *oldmd, SalMediaDescription *newmd) {
int result=0;
int otherdesc_changed;
char *tmp1=NULL;
char *tmp2=NULL;
if (call->params->in_conference != call->current_params->in_conference) return SAL_MEDIA_DESCRIPTION_FORCE_STREAM_RECONSTRUCTION;
if (call->up_bw != linphone_core_get_upload_bandwidth(call->core)) return SAL_MEDIA_DESCRIPTION_FORCE_STREAM_RECONSTRUCTION;
if (call->localdesc_changed) ms_message("Local description has changed: %s", tmp1 = sal_media_description_print_differences(call->localdesc_changed));
otherdesc_changed = sal_media_description_equals(oldmd, newmd);
if (otherdesc_changed) ms_message("Other description has changed: %s", tmp2 = sal_media_description_print_differences(otherdesc_changed));
result = call->localdesc_changed | otherdesc_changed;
if (tmp1) ms_free(tmp1);
if (tmp2) ms_free(tmp2);
return result;
}
void linphone_core_update_streams_destinations(LinphoneCall *call, SalMediaDescription *old_md, SalMediaDescription *new_md) {
SalStreamDescription *new_audiodesc = NULL;
SalStreamDescription *new_videodesc = NULL;
char *rtp_addr, *rtcp_addr;
int i;
for (i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) {
if (!sal_stream_description_active(&new_md->streams[i])) continue;
if (new_md->streams[i].type == SalAudio) {
new_audiodesc = &new_md->streams[i];
} else if (new_md->streams[i].type == SalVideo) {
new_videodesc = &new_md->streams[i];
}
}
if (call->audiostream && new_audiodesc) {
rtp_addr = (new_audiodesc->rtp_addr[0] != '\0') ? new_audiodesc->rtp_addr : new_md->addr;
rtcp_addr = (new_audiodesc->rtcp_addr[0] != '\0') ? new_audiodesc->rtcp_addr : new_md->addr;
ms_message("Change audio stream destination: RTP=%s:%d RTCP=%s:%d", rtp_addr, new_audiodesc->rtp_port, rtcp_addr, new_audiodesc->rtcp_port);
rtp_session_set_remote_addr_full(call->audiostream->ms.sessions.rtp_session, rtp_addr, new_audiodesc->rtp_port, rtcp_addr, new_audiodesc->rtcp_port);
}
#ifdef VIDEO_ENABLED
if (call->videostream && new_videodesc) {
rtp_addr = (new_videodesc->rtp_addr[0] != '\0') ? new_videodesc->rtp_addr : new_md->addr;
rtcp_addr = (new_videodesc->rtcp_addr[0] != '\0') ? new_videodesc->rtcp_addr : new_md->addr;
ms_message("Change video stream destination: RTP=%s:%d RTCP=%s:%d", rtp_addr, new_videodesc->rtp_port, rtcp_addr, new_videodesc->rtcp_port);
rtp_session_set_remote_addr_full(call->videostream->ms.sessions.rtp_session, rtp_addr, new_videodesc->rtp_port, rtcp_addr, new_videodesc->rtcp_port);
}
#else
(void)new_videodesc;
#endif
}
static void _clear_early_media_destinations(LinphoneCall *call, MediaStream *ms){
RtpSession *session=ms->sessions.rtp_session;
rtp_session_clear_aux_remote_addr(session);
if (!call->ice_session) rtp_session_set_symmetric_rtp(session,linphone_core_symmetric_rtp_enabled(call->core));/*restore symmetric rtp if ICE is not used*/
}
static void clear_early_media_destinations(LinphoneCall *call){
if (call->audiostream){
_clear_early_media_destinations(call,(MediaStream*)call->audiostream);
}
if (call->videostream){
_clear_early_media_destinations(call,(MediaStream*)call->videostream);
}
}
static void prepare_early_media_forking(LinphoneCall *call){
/*we need to disable symmetric rtp otherwise our outgoing streams will be switching permanently between the multiple destinations*/
if (call->audiostream){
rtp_session_set_symmetric_rtp(call->audiostream->ms.sessions.rtp_session,FALSE);
}
if (call->videostream){
rtp_session_set_symmetric_rtp(call->videostream->ms.sessions.rtp_session,FALSE);
}
}
void linphone_call_update_frozen_payloads(LinphoneCall *call, SalMediaDescription *result_desc){
SalMediaDescription *local=call->localdesc;
int i;
for(i=0;i<result_desc->nb_streams;++i){
bctbx_list_t *elem;
for (elem=result_desc->streams[i].payloads;elem!=NULL;elem=elem->next){
PayloadType *pt=(PayloadType*)elem->data;
if (is_payload_type_number_available(local->streams[i].already_assigned_payloads, payload_type_get_number(pt), NULL)){
/*new codec, needs to be added to the list*/
local->streams[i].already_assigned_payloads=bctbx_list_append(local->streams[i].already_assigned_payloads, payload_type_clone(pt));
ms_message("LinphoneCall[%p] : payload type %i %s/%i fmtp=%s added to frozen list.",
call, payload_type_get_number(pt), pt->mime_type, pt->clock_rate, pt->recv_fmtp ? pt->recv_fmtp : "");
}
}
}
}
void linphone_call_update_streams(LinphoneCall *call, SalMediaDescription *new_md, LinphoneCallState target_state) {
LinphoneCore *lc = linphone_call_get_core(call);
SalMediaDescription *oldmd = call->resultdesc;
int md_changed = 0;
if (!((call->state == LinphoneCallIncomingEarlyMedia) && (linphone_core_get_ring_during_incoming_early_media(lc)))) {
linphone_core_stop_ringing(lc);
}
if (!new_md) {
ms_error("linphone_call_update_streams() called with null media description");
return;
}
linphone_call_update_biggest_desc(call, call->localdesc);
sal_media_description_ref(new_md);
call->resultdesc = new_md;
if ((call->audiostream && (call->audiostream->ms.state == MSStreamStarted)) || (call->videostream && (call->videostream->ms.state == MSStreamStarted))) {
clear_early_media_destinations(call);
/* We already started media: check if we really need to restart it */
if (oldmd) {
md_changed = media_parameters_changed(call, oldmd, new_md);
if ((md_changed & ( SAL_MEDIA_DESCRIPTION_CODEC_CHANGED
|SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED
|SAL_MEDIA_DESCRIPTION_NETWORK_XXXCAST_CHANGED
|SAL_MEDIA_DESCRIPTION_ICE_RESTART_DETECTED
|SAL_MEDIA_DESCRIPTION_FORCE_STREAM_RECONSTRUCTION ))) {
ms_message("Media descriptions are different, need to restart the streams.");
} else if (call->playing_ringbacktone) {
ms_message("Playing ringback tone, will restart the streams.");
} else {
if (call->all_muted && target_state == LinphoneCallStreamsRunning) {
ms_message("Early media finished, unmuting inputs...");
/* We were in early media, now we want to enable real media */
call->all_muted = FALSE;
if (call->audiostream) linphone_core_enable_mic(lc, linphone_core_mic_enabled(lc));
#ifdef VIDEO_ENABLED
if (call->videostream && call->camera_enabled) {
linphone_call_enable_camera(call, linphone_call_camera_enabled(call));
}
#endif
}
if (md_changed == SAL_MEDIA_DESCRIPTION_UNCHANGED) {
/*FIXME ZRTP, might be restarted in any cases ? */
ms_message("No need to restart streams, SDP is unchanged.");
goto end;
} else {
if (md_changed & SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED) {
ms_message("Network parameters have changed, update them.");
linphone_core_update_streams_destinations(call, oldmd, new_md);
}
if (md_changed & SAL_MEDIA_DESCRIPTION_CRYPTO_KEYS_CHANGED) {
ms_message("Crypto parameters have changed, update them.");
linphone_call_update_crypto_parameters(call, oldmd, new_md);
}
goto end;
}
}
}
linphone_call_stop_media_streams(call);
if (md_changed & SAL_MEDIA_DESCRIPTION_NETWORK_XXXCAST_CHANGED) {
ms_message("Media ip type has changed, destroying sessions context on call [%p]", call);
ms_media_stream_sessions_uninit(&call->sessions[call->main_audio_stream_index]);
ms_media_stream_sessions_uninit(&call->sessions[call->main_video_stream_index]);
ms_media_stream_sessions_uninit(&call->sessions[call->main_text_stream_index]);
}
linphone_call_init_media_streams(call);
}
if (call->audiostream == NULL) {
/* This happens after pausing the call locally. The streams are destroyed and then we wait the 200Ok to recreate them */
linphone_call_init_media_streams(call);
}
if (call->params->real_early_media && (call->state == LinphoneCallOutgoingEarlyMedia)) {
prepare_early_media_forking(call);
}
linphone_call_start_media_streams(call, target_state);
if ((call->state == LinphoneCallPausing) && call->paused_by_app && (bctbx_list_size(lc->calls) == 1)) {
linphone_core_play_named_tone(lc, LinphoneToneCallOnHold);
}
linphone_call_update_frozen_payloads(call, new_md);
end:
if (oldmd) sal_media_description_unref(oldmd);
}
#if 0
static bool_t is_duplicate_call(LinphoneCore *lc, const LinphoneAddress *from, const LinphoneAddress *to){
bctbx_list_t *elem;
for(elem=lc->calls;elem!=NULL;elem=elem->next){
LinphoneCall *call=(LinphoneCall*)elem->data;
if (linphone_address_weak_equal(call->log->from,from) &&
linphone_address_weak_equal(call->log->to, to)){
return TRUE;
}
}
return FALSE;
}
#endif
static bool_t already_a_call_with_remote_address(const LinphoneCore *lc, const LinphoneAddress *remote) {
bctbx_list_t *elem;
ms_message("Searching for already_a_call_with_remote_address.");
for(elem=lc->calls;elem!=NULL;elem=elem->next){
const LinphoneCall *call=(LinphoneCall*)elem->data;
const LinphoneAddress *cRemote=linphone_call_get_remote_address(call);
if (linphone_address_weak_equal(cRemote,remote)) {
ms_warning("already_a_call_with_remote_address found.");
return TRUE;
}
}
return FALSE;
}
static LinphoneCall * look_for_broken_call_to_replace(SalOp *h, LinphoneCore *lc) {
const bctbx_list_t *calls = linphone_core_get_calls(lc);
const bctbx_list_t *it = calls;
while (it != NULL) {
LinphoneCall *replaced_call = NULL;
LinphoneCall *call = (LinphoneCall *)bctbx_list_get_data(it);
SalOp *replaced_op = sal_call_get_replaces(h);
if (replaced_op) replaced_call = (LinphoneCall*)sal_op_get_user_pointer(replaced_op);
if ((call->broken && sal_call_compare_op(h, call->op))
|| ((replaced_call == call) && (strcmp(sal_op_get_from(h), sal_op_get_from(replaced_op)) == 0) && (strcmp(sal_op_get_to(h), sal_op_get_to(replaced_op)) == 0))) {
return call;
}
it = bctbx_list_next(it);
}
return NULL;
}
static void call_received(SalOp *h){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(h));
LinphoneCall *call;
LinphoneCall *replaced_call;
char *alt_contact;
LinphoneAddress *from_addr=NULL;
LinphoneAddress *to_addr=NULL;
LinphoneAddress *from_address_to_search_if_me=NULL; /*address used to know if I'm the caller*/
SalMediaDescription *md;
const char * p_asserted_id;
LinphoneErrorInfo *ei = NULL;
/* Look if this INVITE is for a call that has already been notified but broken because of network failure */
replaced_call = look_for_broken_call_to_replace(h, lc);
if (replaced_call != NULL) {
linphone_call_replace_op(replaced_call, h);
return;
}
p_asserted_id = sal_custom_header_find(sal_op_get_recv_custom_header(h),"P-Asserted-Identity");
/*in some situation, better to trust the network rather than the UAC*/
if (lp_config_get_int(lc->config,"sip","call_logs_use_asserted_id_instead_of_from",0)) {
LinphoneAddress *p_asserted_id_addr;
if (!p_asserted_id) {
ms_warning("No P-Asserted-Identity header found so cannot use it for op [%p] instead of from",h);
} else {
p_asserted_id_addr = linphone_address_new(p_asserted_id);
if (!p_asserted_id_addr) {
ms_warning("Unsupported P-Asserted-Identity header for op [%p] ",h);
} else {
ms_message("Using P-Asserted-Identity [%s] instead of from [%s] for op [%p]",p_asserted_id,sal_op_get_from(h),h);
from_addr=p_asserted_id_addr;
}
}
}
if (!from_addr)
from_addr=linphone_address_new(sal_op_get_from(h));
to_addr=linphone_address_new(sal_op_get_to(h));
/* first check if we can answer successfully to this invite */
if (linphone_presence_model_get_basic_status(lc->presence_model) == LinphonePresenceBasicStatusClosed) {
LinphonePresenceActivity *activity = linphone_presence_model_get_activity(lc->presence_model);
switch (linphone_presence_activity_get_type(activity)) {
case LinphonePresenceActivityPermanentAbsence:
alt_contact = linphone_presence_model_get_contact(lc->presence_model);
if (alt_contact != NULL) {
sal_call_decline(h,SalReasonRedirect,alt_contact);
ms_free(alt_contact);
ei = linphone_error_info_new();
linphone_error_info_set(ei, LinphoneReasonMovedPermanently, 302, "Moved permanently", NULL);
linphone_core_report_early_failed_call(lc, LinphoneCallIncoming, from_addr, to_addr, ei);
sal_op_release(h);
return;
}
break;
default:
/*nothing special to be done*/
break;
}
}
if (!linphone_core_can_we_add_call(lc)){/*busy*/
sal_call_decline(h,SalReasonBusy,NULL);
ei = linphone_error_info_new();
linphone_error_info_set(ei, LinphoneReasonBusy, 486, "Busy - too many calls", NULL);
linphone_core_report_early_failed_call(lc, LinphoneCallIncoming, from_addr, to_addr, ei);
sal_op_release(h);
return;
}
if (sal_op_get_privacy(h) == SalPrivacyNone) {
from_address_to_search_if_me=linphone_address_clone(from_addr);
} else if (p_asserted_id) {
from_address_to_search_if_me = linphone_address_new(p_asserted_id);
} else {
ms_warning ("Hidden from identity, don't know if it's me");
}
if (from_address_to_search_if_me && already_a_call_with_remote_address(lc,from_address_to_search_if_me)){
char *addr = linphone_address_as_string(from_addr);
ms_warning("Receiving a call while one with same address [%s] is initiated, refusing this one with busy message.",addr);
sal_call_decline(h,SalReasonBusy,NULL);
ei = linphone_error_info_new();
linphone_error_info_set(ei, LinphoneReasonBusy, 486, "Busy - duplicated call", NULL);
linphone_core_report_early_failed_call(lc, LinphoneCallIncoming, from_addr, to_addr, ei);
sal_op_release(h);
linphone_address_unref(from_address_to_search_if_me);
ms_free(addr);
return;
} else if (from_address_to_search_if_me) {
linphone_address_unref(from_address_to_search_if_me);
}
call=linphone_call_new_incoming(lc,from_addr,to_addr,h);
linphone_call_make_local_media_description(call);
sal_call_set_local_media_description(call->op,call->localdesc);
md=sal_call_get_final_media_description(call->op);
if (md){
if (sal_media_description_empty(md) || linphone_core_incompatible_security(lc,md)){
ei = linphone_error_info_new();
linphone_error_info_set(ei, LinphoneReasonNotAcceptable, 488, "Not acceptable here", NULL);
linphone_core_report_early_failed_call(lc, LinphoneCallIncoming, linphone_address_ref(from_addr), linphone_address_ref(to_addr), ei);
sal_call_decline(call->op,SalReasonNotAcceptable,NULL);
linphone_call_unref(call);
return;
}
}
/* the call is acceptable so we can now add it to our list */
linphone_core_add_call(lc,call);
linphone_call_ref(call); /*prevent the call from being destroyed while we are notifying, if the user declines within the state callback */
call->bg_task_id=sal_begin_background_task("liblinphone call notification", NULL, NULL);
if (call->defer_notify_incoming) {
/* Defer ringing until the end of the ICE candidates gathering process. */
ms_message("Defer ringing to gather ICE candidates");
return;
}
#ifdef BUILD_UPNP
if ((linphone_core_get_firewall_policy(lc) == LinphonePolicyUseUpnp) && (call->upnp_session != NULL)) {
/* Defer ringing until the end of the ICE candidates gathering process. */
ms_message("Defer ringing to gather uPnP candidates");
return;
}
#endif //BUILD_UPNP
linphone_core_notify_incoming_call(lc,call);
}
static void call_rejected(SalOp *h){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(h));
LinphoneErrorInfo *ei = linphone_error_info_new();
linphone_error_info_from_sal_op(ei, h);
linphone_core_report_early_failed_call(lc, LinphoneCallIncoming, linphone_address_new(sal_op_get_from(h)), linphone_address_new(sal_op_get_to(h)), ei);
}
static void try_early_media_forking(LinphoneCall *call, SalMediaDescription *md){
SalMediaDescription *cur_md=call->resultdesc;
int i;
SalStreamDescription *ref_stream,*new_stream;
ms_message("Early media response received from another branch, checking if media can be forked to this new destination.");
for (i=0;i<SAL_MEDIA_DESCRIPTION_MAX_STREAMS;++i){
if (!sal_stream_description_active(&cur_md->streams[i])) continue;
ref_stream=&cur_md->streams[i];
new_stream=&md->streams[i];
if (ref_stream->type==new_stream->type && ref_stream->payloads && new_stream->payloads){
PayloadType *refpt, *newpt;
refpt=(PayloadType*)ref_stream->payloads->data;
newpt=(PayloadType*)new_stream->payloads->data;
if (strcmp(refpt->mime_type,newpt->mime_type)==0 && refpt->clock_rate==newpt->clock_rate
&& payload_type_get_number(refpt)==payload_type_get_number(newpt)){
MediaStream *ms=NULL;
if (ref_stream->type==SalAudio){
ms=(MediaStream*)call->audiostream;
}else if (ref_stream->type==SalVideo){
ms=(MediaStream*)call->videostream;
}
if (ms){
RtpSession *session=ms->sessions.rtp_session;
const char *rtp_addr=new_stream->rtp_addr[0]!='\0' ? new_stream->rtp_addr : md->addr;
const char *rtcp_addr=new_stream->rtcp_addr[0]!='\0' ? new_stream->rtcp_addr : md->addr;
if (ms_is_multicast(rtp_addr))
ms_message("Multicast addr [%s/%i] does not need auxiliary rtp's destination for call [%p]",
rtp_addr,new_stream->rtp_port,call);
else
rtp_session_add_aux_remote_addr_full(session,rtp_addr,new_stream->rtp_port,rtcp_addr,new_stream->rtcp_port);
}
}
}
}
}
static void start_remote_ring(LinphoneCore *lc, LinphoneCall *call) {
if (lc->sound_conf.play_sndcard!=NULL){
MSSndCard *ringcard=lc->sound_conf.lsd_card ? lc->sound_conf.lsd_card : lc->sound_conf.play_sndcard;
if (call->localdesc->streams[0].max_rate>0) ms_snd_card_set_preferred_sample_rate(ringcard, call->localdesc->streams[0].max_rate);
/*we release sound before playing ringback tone*/
if (call->audiostream)
audio_stream_unprepare_sound(call->audiostream);
if( lc->sound_conf.remote_ring ){
lc->ringstream=ring_start(lc->factory, lc->sound_conf.remote_ring,2000,ringcard);
}
}
}
static void call_ringing(SalOp *h){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(h));
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(h);
SalMediaDescription *md;
if (call==NULL) return;
/*set privacy*/
call->current_params->privacy=(LinphonePrivacyMask)sal_op_get_privacy(call->op);
linphone_core_notify_display_status(lc,_("Remote ringing."));
md=sal_call_get_final_media_description(h);
if (md==NULL){
linphone_core_stop_dtmf_stream(lc);
if (call->state==LinphoneCallOutgoingEarlyMedia){
/*already doing early media */
return;
}
if (lc->ringstream == NULL) start_remote_ring(lc, call);
ms_message("Remote ringing...");
linphone_core_notify_display_status(lc,_("Remote ringing..."));
linphone_call_set_state(call,LinphoneCallOutgoingRinging,"Remote ringing");
}else{
/*initialize the remote call params by invoking linphone_call_get_remote_params(). This is useful as the SDP may not be present in the 200Ok*/
linphone_call_get_remote_params(call);
/*accept early media */
if ((call->audiostream && audio_stream_started(call->audiostream))
#ifdef VIDEO_ENABLED
|| (call->videostream && video_stream_started(call->videostream))
#endif
) {
/*streams already started */
try_early_media_forking(call,md);
#ifdef VIDEO_ENABLED
if (call->videostream){
/*just request for iframe*/
video_stream_send_vfu(call->videostream);
}
#endif
return;
}
linphone_core_notify_show_interface(lc);
linphone_core_notify_display_status(lc,_("Early media."));
linphone_call_set_state(call,LinphoneCallOutgoingEarlyMedia,"Early media");
linphone_core_stop_ringing(lc);
ms_message("Doing early media...");
linphone_call_update_streams(call, md, call->state);
if ((linphone_call_params_get_audio_direction(linphone_call_get_current_params(call)) == LinphoneMediaDirectionInactive) && call->audiostream) {
if (lc->ringstream != NULL) return; /* Already ringing! */
start_remote_ring(lc, call);
}
}
}
static void start_pending_refer(LinphoneCall *call){
linphone_core_start_refered_call(call->core, call,NULL);
}
static void process_call_accepted(LinphoneCore *lc, LinphoneCall *call, SalOp *op){
SalMediaDescription *md, *rmd;
LinphoneCallState next_state = LinphoneCallIdle;
const char *next_state_str = NULL;
LinphoneTaskList tl;
switch (call->state){/*immediately notify the connected state, even if errors occur after*/
case LinphoneCallOutgoingProgress:
case LinphoneCallOutgoingRinging:
case LinphoneCallOutgoingEarlyMedia:
/*immediately notify the connected state*/
linphone_call_set_state(call,LinphoneCallConnected,"Connected");
{
char *tmp=linphone_call_get_remote_address_as_string (call);
char *msg=ms_strdup_printf(_("Call answered by %s"),tmp);
linphone_core_notify_display_status(lc,msg);
ms_free(tmp);
ms_free(msg);
}
break;
default:
break;
}
linphone_task_list_init(&tl);
rmd=sal_call_get_remote_media_description(op);
/*set privacy*/
call->current_params->privacy=(LinphonePrivacyMask)sal_op_get_privacy(call->op);
/*reset the internal call update flag, so it doesn't risk to be copied and used in further re-INVITEs*/
if (call->params->internal_call_update)
call->params->internal_call_update = FALSE;
#ifdef BUILD_UPNP
if (call->upnp_session != NULL && rmd) {
linphone_call_update_upnp_from_remote_media_description(call, rmd);
}
#endif //BUILD_UPNP
md=sal_call_get_final_media_description(op);
if (md == NULL && call->prevstate == LinphoneCallOutgoingEarlyMedia && call->resultdesc != NULL){
ms_message("Using early media SDP since none was received with the 200 OK");
md = call->resultdesc;
}
if (md && (sal_media_description_empty(md) || linphone_core_incompatible_security(lc,md))){
md = NULL;
}
if (md){ /*there is a valid SDP in the response, either offer or answer, and we're able to start/update the streams*/
/* Handle remote ICE attributes if any. */
if (call->ice_session != NULL && rmd) {
linphone_call_update_ice_from_remote_media_description(call, rmd, !sal_call_is_offerer(op));
}
switch (call->state){
case LinphoneCallResuming:
linphone_core_notify_display_status(lc,_("Call resumed."));
/*intentionally no break*/
case LinphoneCallConnected:
if (call->referer) linphone_core_notify_refer_state(lc,call->referer,call);
/*intentionally no break*/
case LinphoneCallUpdating:
case LinphoneCallUpdatedByRemote:
if (!sal_media_description_has_dir(call->localdesc, SalStreamInactive) &&
(sal_media_description_has_dir(md,SalStreamRecvOnly) ||
sal_media_description_has_dir(md,SalStreamInactive))){
next_state = LinphoneCallPausedByRemote;
next_state_str = "Call paused by remote";
}else{
if (!call->params->in_conference)
lc->current_call=call;
next_state = LinphoneCallStreamsRunning;
next_state_str = "Streams running";
}
break;
case LinphoneCallEarlyUpdating:
next_state_str = "Early update accepted";
next_state = call->prevstate;
break;
case LinphoneCallPausing:
/*when we entered the pausing state, we always reach the paused state whatever the content of the remote SDP is.
Our streams are all send-only (with music), soundcard and camera are never used*/
next_state = LinphoneCallPaused;
next_state_str = "Call paused";
if (call->refer_pending)
linphone_task_list_add(&tl, (LinphoneCoreIterateHook)start_pending_refer, call);
break;
default:
ms_error("call_accepted(): don't know what to do in state [%s]", linphone_call_state_to_string(call->state));
break;
}
if (next_state != LinphoneCallIdle){
linphone_call_update_remote_session_id_and_ver(call);
linphone_call_update_ice_state_in_call_stats(call);
linphone_call_update_streams(call, md, next_state);
linphone_call_fix_call_parameters(call, rmd);
linphone_call_set_state(call, next_state, next_state_str);
}else{
ms_error("BUG: next_state is not set in call_accepted(), current state is %s", linphone_call_state_to_string(call->state));
}
}else{ /*invalid or no SDP*/
switch (call->prevstate){
/*send a bye only in case of early states*/
case LinphoneCallOutgoingInit:
case LinphoneCallOutgoingProgress:
case LinphoneCallOutgoingRinging:
case LinphoneCallOutgoingEarlyMedia:
case LinphoneCallIncomingReceived:
case LinphoneCallIncomingEarlyMedia:
ms_error("Incompatible SDP answer received, need to abort the call");
linphone_call_abort(call, _("Incompatible, check codecs or security settings..."));
break;
/*otherwise we are able to resume previous state*/
default:
ms_error("Incompatible SDP answer received");
switch(call->state) {
case LinphoneCallPausedByRemote:
break;
case LinphoneCallPaused:
break;
case LinphoneCallStreamsRunning:
break;
default:
ms_message("Incompatible SDP answer received, restoring previous state [%s]",linphone_call_state_to_string(call->prevstate));
linphone_call_set_state(call,call->prevstate,_("Incompatible media parameters."));
break;
}
break;
}
}
linphone_task_list_run(&tl);
linphone_task_list_free(&tl);
}
/*
* could be reach :
* - when the call is accepted
* - when a request is accepted (pause, resume)
*/
static void call_accepted(SalOp *op){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
if (call == NULL){
ms_warning("call_accepted: call does no longer exist.");
return ;
}
process_call_accepted(lc, call, op);
}
static void call_resumed(LinphoneCore *lc, LinphoneCall *call){
linphone_core_notify_display_status(lc,_("We have been resumed."));
_linphone_call_accept_update(call,NULL,LinphoneCallStreamsRunning,"Connected (streams running)");
}
static void call_paused_by_remote(LinphoneCore *lc, LinphoneCall *call){
LinphoneCallParams *params;
/* we are being paused */
linphone_core_notify_display_status(lc,_("We are paused by other party."));
params = linphone_call_params_copy(call->params);
if (lp_config_get_int(lc->config, "sip", "inactive_video_on_pause", 0)) {
linphone_call_params_set_video_direction(params, LinphoneMediaDirectionInactive);
}
_linphone_call_accept_update(call,params,LinphoneCallPausedByRemote,"Call paused by remote");
linphone_call_params_unref(params);
}
static void call_updated_by_remote(LinphoneCore *lc, LinphoneCall *call){
linphone_core_notify_display_status(lc,_("Call is updated by remote."));
linphone_call_set_state(call, LinphoneCallUpdatedByRemote,"Call updated by remote");
if (call->defer_update == FALSE){
if (call->state == LinphoneCallUpdatedByRemote){
linphone_call_accept_update(call, NULL);
}else{
/*otherwise it means that the app responded by linphone_core_accept_call_update
* within the callback, so job is already done.*/
}
}else{
if (call->state == LinphoneCallUpdatedByRemote){
ms_message("LinphoneCall [%p]: UpdatedByRemoted was signaled but defered. LinphoneCore expects the application to call "
"linphone_core_accept_call_update() later.", call);
}
}
}
/* this callback is called when an incoming re-INVITE/ SIP UPDATE modifies the session*/
static void call_updated(LinphoneCore *lc, LinphoneCall *call, SalOp *op, bool_t is_update){
SalMediaDescription *rmd=sal_call_get_remote_media_description(op);
call->defer_update = lp_config_get_int(lc->config, "sip", "defer_update_default", FALSE);
switch(call->state){
case LinphoneCallPausedByRemote:
if (sal_media_description_has_dir(rmd,SalStreamSendRecv) || sal_media_description_has_dir(rmd,SalStreamRecvOnly)){
call_resumed(lc,call);
}else{
call_updated_by_remote(lc, call);
}
break;
/*SIP UPDATE CASE*/
case LinphoneCallOutgoingRinging:
case LinphoneCallOutgoingEarlyMedia:
case LinphoneCallIncomingEarlyMedia:
if (is_update) {
linphone_call_set_state(call, LinphoneCallEarlyUpdatedByRemote, "EarlyUpdatedByRemote");
_linphone_call_accept_update(call,NULL,call->prevstate,linphone_call_state_to_string(call->prevstate));
}
break;
case LinphoneCallStreamsRunning:
case LinphoneCallConnected:
case LinphoneCallUpdatedByRemote: // Can happen on UAC connectivity loss
if (sal_media_description_has_dir(rmd,SalStreamSendOnly) || sal_media_description_has_dir(rmd,SalStreamInactive)){
call_paused_by_remote(lc,call);
}else{
call_updated_by_remote(lc, call);
}
break;
case LinphoneCallPaused:
/*we'll remain in pause state but accept the offer anyway according to default parameters*/
_linphone_call_accept_update(call,NULL,call->state,linphone_call_state_to_string(call->state));
break;
case LinphoneCallUpdating:
case LinphoneCallPausing:
case LinphoneCallResuming:
sal_call_decline(call->op,SalReasonInternalError,NULL);
/*no break*/
case LinphoneCallIdle:
case LinphoneCallOutgoingInit:
case LinphoneCallEnd:
case LinphoneCallIncomingReceived:
case LinphoneCallOutgoingProgress:
case LinphoneCallRefered:
case LinphoneCallError:
case LinphoneCallReleased:
case LinphoneCallEarlyUpdatedByRemote:
case LinphoneCallEarlyUpdating:
ms_warning("Receiving reINVITE or UPDATE while in state [%s], should not happen.",linphone_call_state_to_string(call->state));
break;
}
}
/* this callback is called when an incoming re-INVITE/ SIP UPDATE modifies the session*/
static void call_updating(SalOp *op, bool_t is_update){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
SalMediaDescription *rmd=sal_call_get_remote_media_description(op);
if (!call) {
ms_error("call_updating(): call doesn't exist anymore");
return ;
}
linphone_call_fix_call_parameters(call, rmd);
if (call->state!=LinphoneCallPaused){
/*Refresh the local description, but in paused state, we don't change anything.*/
if (rmd == NULL && lp_config_get_int(call->core->config,"sip","sdp_200_ack_follow_video_policy",0)) {
LinphoneCallParams *p=linphone_core_create_call_params(lc, NULL);
ms_message("Applying default policy for offering SDP on call [%p]",call);
linphone_call_set_new_params(call, p);
linphone_call_params_unref(p);
}
linphone_call_make_local_media_description(call);
sal_call_set_local_media_description(call->op,call->localdesc);
}
if (rmd == NULL){
/* case of a reINVITE or UPDATE without SDP */
call->expect_media_in_ack = TRUE;
sal_call_accept(op); /*respond with an offer*/
/*don't do anything else in this case, wait for the ACK to receive to notify the app*/
}else {
SalMediaDescription *md;
SalMediaDescription *prev_result_desc=call->resultdesc;
call->expect_media_in_ack = FALSE;
md=sal_call_get_final_media_description(call->op);
if (md && (sal_media_description_empty(md) || linphone_core_incompatible_security(lc,md))){
sal_call_decline(call->op,SalReasonNotAcceptable,NULL);
return;
}
if (is_update && prev_result_desc && md){
int diff=sal_media_description_equals(prev_result_desc,md);
if (diff & (SAL_MEDIA_DESCRIPTION_CRYPTO_POLICY_CHANGED|SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED)){
ms_warning("Cannot accept this update, it is changing parameters that require user approval");
sal_call_decline(call->op,SalReasonNotAcceptable,NULL); /*FIXME should send 504 Cannot change the session parameters without prompting the user"*/
return;
}
}
call_updated(lc, call, op, is_update);
}
}
static void call_ack(SalOp *op){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
if (call == NULL){
ms_warning("call_ack(): no call for which an ack is expected");
return;
}
if (call->expect_media_in_ack){
switch(call->state){
case LinphoneCallStreamsRunning:
case LinphoneCallPausedByRemote:
linphone_call_set_state(call, LinphoneCallUpdatedByRemote, "UpdatedByRemote");
break;
default:
break;
}
process_call_accepted(lc, call, op);
}
}
static void call_terminated(SalOp *op, const char *from){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
if (call==NULL) return;
switch(linphone_call_get_state(call)){
case LinphoneCallEnd:
case LinphoneCallError:
ms_warning("call_terminated: already terminated, ignoring.");
return;
break;
case LinphoneCallIncomingReceived:
case LinphoneCallIncomingEarlyMedia:
linphone_error_info_set(call->ei, LinphoneReasonNotAnswered, 0, "Incoming call cancelled", NULL);
call->non_op_error = TRUE;
break;
default:
break;
}
ms_message("Current call terminated...");
if (call->refer_pending){
linphone_core_start_refered_call(lc,call,NULL);
}
//we stop the call only if we have this current call or if we are in call
if ((bctbx_list_size(lc->calls) == 1) || linphone_core_in_call(lc)) {
linphone_core_stop_ringing(lc);
}
linphone_call_stop_media_streams(call);
linphone_core_notify_show_interface(lc);
linphone_core_notify_display_status(lc,_("Call terminated."));
#ifdef BUILD_UPNP
linphone_call_delete_upnp_session(call);
#endif //BUILD_UPNP
linphone_call_set_state(call, LinphoneCallEnd,"Call ended");
}
static int resume_call_after_failed_transfer(LinphoneCall *call){
if (call->was_automatically_paused && call->state==LinphoneCallPausing)
return BELLE_SIP_CONTINUE; /*was still in pausing state*/
if (call->was_automatically_paused && call->state==LinphoneCallPaused){
if (sal_op_is_idle(call->op)){
linphone_call_resume(call);
}else {
ms_message("resume_call_after_failed_transfer(), salop was busy");
return BELLE_SIP_CONTINUE;
}
}
linphone_call_unref(call);
return BELLE_SIP_STOP;
}
static void call_failure(SalOp *op){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
const SalErrorInfo *ei=sal_op_get_error_info(op);
char *msg486=_("User is busy.");
char *msg480=_("User is temporarily unavailable.");
/*char *retrymsg=_("%s. Retry after %i minute(s).");*/
char *msg600=_("User does not want to be disturbed.");
char *msg603=_("Call declined.");
const char *msg=ei->full_string;
LinphoneCall *referer;
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
if (call==NULL){
ms_warning("Call faillure reported on already terminated call.");
return ;
}
referer=call->referer;
linphone_core_notify_show_interface(lc);
switch(ei->reason){
case SalReasonNone:
break;
case SalReasonRequestTimeout:
msg=_("Request timeout.");
linphone_core_notify_display_status(lc,msg);
break;
case SalReasonDeclined:
msg=msg603;
linphone_core_notify_display_status(lc,msg603);
break;
case SalReasonBusy:
msg=msg486;
linphone_core_notify_display_status(lc,msg486);
break;
case SalReasonRedirect:
{
linphone_call_stop_media_streams(call);
if ( call->state==LinphoneCallOutgoingInit
|| call->state==LinphoneCallOutgoingProgress
|| call->state==LinphoneCallOutgoingRinging /*push case*/
|| call->state==LinphoneCallOutgoingEarlyMedia){
LinphoneAddress* redirection_to = (LinphoneAddress*)sal_op_get_remote_contact_address(call->op);
if( redirection_to ){
char* url = linphone_address_as_string(redirection_to);
ms_warning("Redirecting call [%p] to %s",call, url);
ms_free(url);
if( call->log->to != NULL ) {
linphone_address_unref(call->log->to);
}
call->log->to = linphone_address_ref(redirection_to);
linphone_call_restart_invite(call);
return;
}
}
msg=_("Redirected");
linphone_core_notify_display_status(lc,msg);
}
break;
case SalReasonTemporarilyUnavailable:
msg=msg480;
linphone_core_notify_display_status(lc,msg480);
break;
case SalReasonNotFound:
linphone_core_notify_display_status(lc,msg);
break;
case SalReasonDoNotDisturb:
msg=msg600;
linphone_core_notify_display_status(lc,msg600);
break;
case SalReasonUnsupportedContent: /*<this is for compatibility: linphone sent 415 because of SDP offer answer failure*/
case SalReasonNotAcceptable:
ms_message("Outgoing call [%p] failed with SRTP and/or AVPF enabled", call);
if ((call->state == LinphoneCallOutgoingInit)
|| (call->state == LinphoneCallOutgoingProgress)
|| (call->state == LinphoneCallOutgoingRinging) /* Push notification case */
|| (call->state == LinphoneCallOutgoingEarlyMedia)) {
int i;
for (i = 0; i < call->localdesc->nb_streams; i++) {
if (!sal_stream_description_active(&call->localdesc->streams[i])) continue;
if (call->params->media_encryption == LinphoneMediaEncryptionSRTP) {
if (call->params->avpf_enabled == TRUE) {
if (i == 0) ms_message("Retrying call [%p] with SAVP", call);
call->params->avpf_enabled = FALSE;
linphone_call_restart_invite(call);
return;
} else if (!linphone_core_is_media_encryption_mandatory(lc)) {
if (i == 0) ms_message("Retrying call [%p] with AVP", call);
call->params->media_encryption = LinphoneMediaEncryptionNone;
memset(call->localdesc->streams[i].crypto, 0, sizeof(call->localdesc->streams[i].crypto));
linphone_call_restart_invite(call);
return;
}
} else if (call->params->avpf_enabled == TRUE) {
if (i == 0) ms_message("Retrying call [%p] with AVP", call);
call->params->avpf_enabled = FALSE;
linphone_call_restart_invite(call);
return;
}
}
}
msg=_("Incompatible media parameters.");
linphone_core_notify_display_status(lc,msg);
break;
default:
linphone_core_notify_display_status(lc,_("Call failed."));
}
/*some call errors are not fatal*/
switch (call->state) {
case LinphoneCallUpdating:
case LinphoneCallPausing:
case LinphoneCallResuming:
if (ei->reason != SalReasonNoMatch){
ms_message("Call error on state [%s], restoring previous state",linphone_call_state_to_string(call->prevstate));
linphone_call_set_state(call, call->prevstate,ei->full_string);
return;
}
default:
break; /*nothing to do*/
}
linphone_core_stop_ringing(lc);
linphone_call_stop_media_streams(call);
#ifdef BUILD_UPNP
linphone_call_delete_upnp_session(call);
#endif //BUILD_UPNP
if (call->state!=LinphoneCallEnd && call->state!=LinphoneCallError){
if (ei->reason==SalReasonDeclined){
linphone_call_set_state(call,LinphoneCallEnd,"Call declined.");
}else{
if (linphone_call_state_is_early(call->state)){
linphone_call_set_state(call,LinphoneCallError,ei->full_string);
}else{
linphone_call_set_state(call, LinphoneCallEnd, ei->full_string);
}
}
if (ei->reason!=SalReasonNone) linphone_core_play_call_error_tone(lc,linphone_reason_from_sal(ei->reason));
}
if (referer){
/*notify referer of the failure*/
linphone_core_notify_refer_state(lc,referer,call);
/*schedule automatic resume of the call. This must be done only after the notifications are completed due to dialog serialization of requests.*/
linphone_core_queue_task(lc,(belle_sip_source_func_t)resume_call_after_failed_transfer,linphone_call_ref(referer),"Automatic call resuming after failed transfer");
}
}
static void call_released(SalOp *op){
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
if (call!=NULL){
linphone_call_set_state(call,LinphoneCallReleased,"Call released");
}else{
/*we can arrive here when the core manages call at Sal level without creating a LinphoneCall object. Typicially:
* - when declining an incoming call with busy because maximum number of calls is reached.
*/
}
}
static void call_cancel_done(SalOp *op) {
LinphoneCall *call = (LinphoneCall *)sal_op_get_user_pointer(op);
if (call->reinvite_on_cancel_response_requested == TRUE) {
call->reinvite_on_cancel_response_requested = FALSE;
linphone_call_reinvite_to_recover_from_connection_loss(call);
}
}
static void auth_failure(SalOp *op, SalAuthInfo* info) {
LinphoneCore *lc = (LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
LinphoneAuthInfo *ai = NULL;
if (info != NULL) {
ai = (LinphoneAuthInfo*)_linphone_core_find_auth_info(lc, info->realm, info->username, info->domain, TRUE);
if (ai){
LinphoneAuthMethod method = info->mode == SalAuthModeHttpDigest ? LinphoneAuthHttpDigest : LinphoneAuthTls;
LinphoneAuthInfo *auth_info = linphone_core_create_auth_info(lc, info->username, NULL, NULL, NULL, info->realm, info->domain);
ms_message("%s/%s/%s/%s authentication fails.", info->realm, info->username, info->domain, info->mode == SalAuthModeHttpDigest ? "HttpDigest" : "Tls");
/*ask again for password if auth info was already supplied but apparently not working*/
linphone_core_notify_authentication_requested(lc, auth_info, method);
linphone_auth_info_unref(auth_info);
// Deprecated
linphone_core_notify_auth_info_requested(lc, info->realm, info->username, info->domain);
}
}
}
static void register_success(SalOp *op, bool_t registered){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)sal_op_get_user_pointer(op);
char *msg;
if (!cfg){
ms_message("Registration success for deleted proxy config, ignored");
return;
}
linphone_proxy_config_set_state(cfg, registered ? LinphoneRegistrationOk : LinphoneRegistrationCleared ,
registered ? "Registration successful" : "Unregistration done");
{
if (registered) msg=ms_strdup_printf(_("Registration on %s successful."),sal_op_get_proxy(op));
else msg=ms_strdup_printf(_("Unregistration on %s done."),sal_op_get_proxy(op));
linphone_core_notify_display_status(lc,msg);
ms_free(msg);
}
}
static void register_failure(SalOp *op){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)sal_op_get_user_pointer(op);
const SalErrorInfo *ei=sal_op_get_error_info(op);
const char *details=ei->full_string;
if (cfg==NULL){
ms_warning("Registration failed for unknown proxy config.");
return ;
}
if (details==NULL)
details=_("no response timeout");
{
char *msg=ortp_strdup_printf(_("Registration on %s failed: %s"),sal_op_get_proxy(op), details);
linphone_core_notify_display_status(lc,msg);
ms_free(msg);
}
if ((ei->reason == SalReasonServiceUnavailable || ei->reason == SalReasonIOError)
&& linphone_proxy_config_get_state(cfg) == LinphoneRegistrationOk) {
linphone_proxy_config_set_state(cfg,LinphoneRegistrationProgress,_("Service unavailable, retrying"));
} else {
linphone_proxy_config_set_state(cfg,LinphoneRegistrationFailed,details);
}
if (cfg->long_term_event){
/*prevent publish to be sent now until registration gets successful*/
linphone_event_terminate(cfg->long_term_event);
linphone_event_unref(cfg->long_term_event);
cfg->long_term_event=NULL;
cfg->send_publish=cfg->publish;
}
}
static void vfu_request(SalOp *op){
#ifdef VIDEO_ENABLED
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer (op);
if (call==NULL){
ms_warning("VFU request but no call !");
return ;
}
if (call->videostream)
video_stream_send_vfu(call->videostream);
#endif
}
static void dtmf_received(SalOp *op, char dtmf){
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
if (!call) return;
linphone_call_notify_dtmf_received(call, dtmf);
}
static void refer_received(Sal *sal, SalOp *op, const char *referto){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal);
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
LinphoneAddress *refer_to_addr = linphone_address_new(referto);
char method[20] = "";
if(refer_to_addr) {
const char *tmp = linphone_address_get_method_param(refer_to_addr);
if(tmp) strncpy(method, tmp, sizeof(method));
linphone_address_unref(refer_to_addr);
}
if (call && (strlen(method) == 0 || strcmp(method, "INVITE") == 0)) {
if (call->refer_to!=NULL){
ms_free(call->refer_to);
}
call->refer_to=ms_strdup(referto);
call->refer_pending=TRUE;
linphone_call_set_state(call,LinphoneCallRefered,"Refered");
{
char *msg=ms_strdup_printf(_("We are transferred to %s"),referto);
linphone_core_notify_display_status(lc,msg);
ms_free(msg);
}
if (call->refer_pending) linphone_core_start_refered_call(lc,call,NULL);
}else {
linphone_core_notify_refer_received(lc,referto);
}
}
static void message_received(SalOp *op, const SalMessage *msg){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
LinphoneReason reason = lc->chat_deny_code;
if (reason == LinphoneReasonNone) {
linphone_core_message_received(lc, op, msg);
}
sal_message_reply(op, linphone_reason_to_sal(reason));
if (!call) sal_op_release(op);
}
static void parse_presence_requested(SalOp *op, const char *content_type, const char *content_subtype, const char *body, SalPresenceModel **result) {
linphone_notify_parse_presence(content_type, content_subtype, body, result);
}
static void convert_presence_to_xml_requested(SalOp *op, SalPresenceModel *presence, const char *contact, char **content) {
/*for backward compatibility because still used by notify. No loguer used for publish*/
if(linphone_presence_model_get_presentity((LinphonePresenceModel*)presence) == NULL) {
LinphoneAddress * presentity = linphone_address_new(contact);
linphone_presence_model_set_presentity((LinphonePresenceModel*)presence, presentity);
linphone_address_unref(presentity);
}
*content = linphone_presence_model_to_xml((LinphonePresenceModel*)presence);
}
static void notify_presence(SalOp *op, SalSubscribeStatus ss, SalPresenceModel *model, const char *msg){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
linphone_notify_recv(lc,op,ss,model);
}
static void subscribe_presence_received(SalOp *op, const char *from){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
linphone_subscription_new(lc,op,from);
}
static void subscribe_presence_closed(SalOp *op, const char *from){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
linphone_subscription_closed(lc,op);
}
static void ping_reply(SalOp *op){
LinphoneCall *call=(LinphoneCall*) sal_op_get_user_pointer(op);
ms_message("ping reply !");
if (call){
if (call->state==LinphoneCallOutgoingInit){
call->ping_replied=TRUE;
linphone_call_proceed_with_invite_if_ready(call, NULL);
}
}
else
{
ms_warning("ping reply without call attached...");
}
}
static bool_t fill_auth_info_with_client_certificate(LinphoneCore *lc, SalAuthInfo* sai) {
const char *chain_file = linphone_core_get_tls_cert_path(lc);
const char *key_file = linphone_core_get_tls_key_path(lc);
if (key_file && chain_file) {
#ifndef _WIN32
// optinal check for files
struct stat st;
if (stat(key_file, &st)) {
ms_warning("No client certificate key found in %s", key_file);
return FALSE;
}
if (stat(chain_file, &st)) {
ms_warning("No client certificate chain found in %s", chain_file);
return FALSE;
}
#endif
sal_certificates_chain_parse_file(sai, chain_file, SAL_CERTIFICATE_RAW_FORMAT_PEM);
sal_signing_key_parse_file(sai, key_file, "");
} else if (lc->tls_cert && lc->tls_key) {
sal_certificates_chain_parse(sai, lc->tls_cert, SAL_CERTIFICATE_RAW_FORMAT_PEM);
sal_signing_key_parse(sai, lc->tls_key, "");
}
return sai->certificates && sai->key;
}
static bool_t fill_auth_info(LinphoneCore *lc, SalAuthInfo* sai) {
LinphoneAuthInfo *ai = NULL;
if (sai->mode == SalAuthModeTls) {
ai = (LinphoneAuthInfo*)_linphone_core_find_tls_auth_info(lc);
} else {
ai = (LinphoneAuthInfo*)_linphone_core_find_auth_info(lc,sai->realm,sai->username,sai->domain, FALSE);
}
if (ai) {
if (sai->mode == SalAuthModeHttpDigest) {
sai->userid = ms_strdup(ai->userid ? ai->userid : ai->username);
sai->password = ai->passwd?ms_strdup(ai->passwd) : NULL;
sai->ha1 = ai->ha1 ? ms_strdup(ai->ha1) : NULL;
} else if (sai->mode == SalAuthModeTls) {
if (ai->tls_cert && ai->tls_key) {
sal_certificates_chain_parse(sai, ai->tls_cert, SAL_CERTIFICATE_RAW_FORMAT_PEM);
sal_signing_key_parse(sai, ai->tls_key, "");
} else if (ai->tls_cert_path && ai->tls_key_path) {
sal_certificates_chain_parse_file(sai, ai->tls_cert_path, SAL_CERTIFICATE_RAW_FORMAT_PEM);
sal_signing_key_parse_file(sai, ai->tls_key_path, "");
} else {
fill_auth_info_with_client_certificate(lc, sai);
}
}
if (sai->realm && !ai->realm){
/*if realm was not known, then set it so that ha1 may eventually be calculated and clear text password dropped*/
linphone_auth_info_set_realm(ai, sai->realm);
linphone_core_write_auth_info(lc, ai);
}
return TRUE;
} else {
if (sai->mode == SalAuthModeTls) {
return fill_auth_info_with_client_certificate(lc, sai);
}
return FALSE;
}
}
static bool_t auth_requested(Sal* sal, SalAuthInfo* sai) {
LinphoneCore *lc = (LinphoneCore *)sal_get_user_pointer(sal);
if (fill_auth_info(lc,sai)) {
return TRUE;
} else {
LinphoneAuthMethod method = sai->mode == SalAuthModeHttpDigest ? LinphoneAuthHttpDigest : LinphoneAuthTls;
LinphoneAuthInfo *ai = linphone_core_create_auth_info(lc, sai->username, NULL, NULL, NULL, sai->realm, sai->domain);
linphone_core_notify_authentication_requested(lc, ai, method);
linphone_auth_info_unref(ai);
// Deprecated
linphone_core_notify_auth_info_requested(lc, sai->realm, sai->username, sai->domain);
if (fill_auth_info(lc, sai)) {
return TRUE;
}
return FALSE;
}
}
static void notify_refer(SalOp *op, SalReferStatus status){
LinphoneCall *call=(LinphoneCall*) sal_op_get_user_pointer(op);
LinphoneCallState cstate;
if (call==NULL) {
ms_warning("Receiving notify_refer for unknown call.");
return ;
}
switch(status){
case SalReferTrying:
cstate=LinphoneCallOutgoingProgress;
break;
case SalReferSuccess:
cstate=LinphoneCallConnected;
break;
case SalReferFailed:
cstate=LinphoneCallError;
break;
default:
cstate=LinphoneCallError;
}
linphone_call_set_transfer_state(call, cstate);
if (cstate==LinphoneCallConnected){
/*automatically terminate the call as the transfer is complete.*/
linphone_call_terminate(call);
}
}
static LinphoneChatMessageState chatStatusSal2Linphone(SalMessageDeliveryStatus status){
switch(status){
case SalMessageDeliveryInProgress:
return LinphoneChatMessageStateInProgress;
case SalMessageDeliveryDone:
return LinphoneChatMessageStateDelivered;
case SalMessageDeliveryFailed:
return LinphoneChatMessageStateNotDelivered;
}
return LinphoneChatMessageStateIdle;
}
static void message_delivery_update(SalOp *op, SalMessageDeliveryStatus status){
LinphoneChatMessage *chat_msg=(LinphoneChatMessage* )sal_op_get_user_pointer(op);
if (chat_msg == NULL) {
// Do not handle delivery status for isComposing messages.
return;
}
// check that the message does not belong to an already destroyed chat room - if so, do not invoke callbacks
if (chat_msg->chat_room != NULL) {
linphone_chat_message_update_state(chat_msg, chatStatusSal2Linphone(status));
}
if (status != SalMessageDeliveryInProgress) { /*only release op if not in progress*/
linphone_chat_message_destroy(chat_msg);
}
}
static void info_received(SalOp *op, SalBodyHandler *body_handler){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
linphone_core_notify_info_message(lc,op,body_handler);
}
static void subscribe_response(SalOp *op, SalSubscribeStatus status, int will_retry){
LinphoneEvent *lev=(LinphoneEvent*)sal_op_get_user_pointer(op);
if (lev==NULL) return;
if (status==SalSubscribeActive){
linphone_event_set_state(lev,LinphoneSubscriptionActive);
}else if (status==SalSubscribePending){
linphone_event_set_state(lev,LinphoneSubscriptionPending);
}else{
if (will_retry){
linphone_event_set_state(lev,LinphoneSubscriptionOutgoingProgress);
}
else linphone_event_set_state(lev,LinphoneSubscriptionError);
}
}
static void notify(SalOp *op, SalSubscribeStatus st, const char *eventname, SalBodyHandler *body_handler){
LinphoneEvent *lev=(LinphoneEvent*)sal_op_get_user_pointer(op);
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
bool_t out_of_dialog = (lev==NULL);
if (out_of_dialog) {
/*out of dialog notify */
lev = linphone_event_new_with_out_of_dialog_op(lc,op,LinphoneSubscriptionOutgoing,eventname);
}
{
LinphoneContent *ct=linphone_content_from_sal_body_handler(body_handler);
if (ct) {
linphone_core_notify_notify_received(lc,lev,eventname,ct);
linphone_content_unref(ct);
}
}
if (out_of_dialog){
/*out of dialog NOTIFY do not create an implicit subscription*/
linphone_event_set_state(lev, LinphoneSubscriptionTerminated);
}else if (st!=SalSubscribeNone){
linphone_event_set_state(lev,linphone_subscription_state_from_sal(st));
}
}
static void subscribe_received(SalOp *op, const char *eventname, const SalBodyHandler *body_handler){
LinphoneEvent *lev=(LinphoneEvent*)sal_op_get_user_pointer(op);
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
if (lev==NULL) {
lev=linphone_event_new_with_op(lc,op,LinphoneSubscriptionIncoming,eventname);
linphone_event_set_state(lev,LinphoneSubscriptionIncomingReceived);
}else{
/*subscribe refresh, unhandled*/
}
}
static void incoming_subscribe_closed(SalOp *op){
LinphoneEvent *lev=(LinphoneEvent*)sal_op_get_user_pointer(op);
linphone_event_set_state(lev,LinphoneSubscriptionTerminated);
}
static void on_publish_response(SalOp* op){
LinphoneEvent *lev=(LinphoneEvent*)sal_op_get_user_pointer(op);
const SalErrorInfo *ei=sal_op_get_error_info(op);
if (lev==NULL) return;
if (ei->reason==SalReasonNone){
if (!lev->terminating)
linphone_event_set_publish_state(lev,LinphonePublishOk);
else
linphone_event_set_publish_state(lev,LinphonePublishCleared);
}else{
if (lev->publish_state==LinphonePublishOk){
linphone_event_set_publish_state(lev,LinphonePublishProgress);
}else{
linphone_event_set_publish_state(lev,LinphonePublishError);
}
}
}
static void on_expire(SalOp *op){
LinphoneEvent *lev=(LinphoneEvent*)sal_op_get_user_pointer(op);
if (lev==NULL) return;
if (linphone_event_get_publish_state(lev)==LinphonePublishOk){
linphone_event_set_publish_state(lev,LinphonePublishExpiring);
}else if (linphone_event_get_subscription_state(lev)==LinphoneSubscriptionActive){
linphone_event_set_state(lev,LinphoneSubscriptionExpiring);
}
}
static void on_notify_response(SalOp *op){
LinphoneEvent *lev=(LinphoneEvent*)sal_op_get_user_pointer(op);
if (lev==NULL) return;
/*this is actually handling out of dialogs notify - for the moment*/
if (!lev->is_out_of_dialog_op) return;
switch (linphone_event_get_subscription_state(lev)){
case LinphoneSubscriptionIncomingReceived:
if (sal_op_get_error_info(op)->reason == SalReasonNone){
linphone_event_set_state(lev, LinphoneSubscriptionTerminated);
}else{
linphone_event_set_state(lev, LinphoneSubscriptionError);
}
break;
default:
ms_warning("Unhandled on_notify_response() case %s", linphone_subscription_state_to_string(linphone_event_get_subscription_state(lev)));
}
}
SalCallbacks linphone_sal_callbacks={
call_received,
call_rejected,
call_ringing,
call_accepted,
call_ack,
call_updating,
call_terminated,
call_failure,
call_released,
call_cancel_done,
auth_failure,
register_success,
register_failure,
vfu_request,
dtmf_received,
refer_received,
message_received,
message_delivery_update,
notify_refer,
subscribe_received,
incoming_subscribe_closed,
subscribe_response,
notify,
subscribe_presence_received,
subscribe_presence_closed,
parse_presence_requested,
convert_presence_to_xml_requested,
notify_presence,
ping_reply,
auth_requested,
info_received,
on_publish_response,
on_expire,
on_notify_response
};