forked from mirrors/linphone-iphone
1007 lines
33 KiB
C++
1007 lines
33 KiB
C++
/*
|
|
* call-session.cpp
|
|
* Copyright (C) 2017 Belledonne Communications SARL
|
|
*
|
|
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <bctoolbox/defs.h>
|
|
|
|
#include "c-wrapper/c-wrapper.h"
|
|
|
|
#include "address/address-p.h"
|
|
#include "conference/session/call-session-p.h"
|
|
#include "call/call-p.h"
|
|
#include "conference/params/call-session-params-p.h"
|
|
|
|
#include "conference/session/call-session.h"
|
|
|
|
#include "logger/logger.h"
|
|
|
|
#include "linphone/core.h"
|
|
|
|
#include "private.h"
|
|
|
|
using namespace std;
|
|
|
|
LINPHONE_BEGIN_NAMESPACE
|
|
|
|
// =============================================================================
|
|
|
|
CallSessionPrivate::CallSessionPrivate (const Conference &conference, const CallSessionParams *params, CallSessionListener *listener)
|
|
: conference(conference), listener(listener) {
|
|
if (params)
|
|
this->params = new CallSessionParams(*params);
|
|
currentParams = new CallSessionParams();
|
|
core = conference.getCore();
|
|
ei = linphone_error_info_new();
|
|
}
|
|
|
|
CallSessionPrivate::~CallSessionPrivate () {
|
|
if (currentParams)
|
|
delete currentParams;
|
|
if (params)
|
|
delete params;
|
|
if (remoteParams)
|
|
delete remoteParams;
|
|
if (ei)
|
|
linphone_error_info_unref(ei);
|
|
if (log)
|
|
linphone_call_log_unref(log);
|
|
if (op)
|
|
sal_op_release(op);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
int CallSessionPrivate::computeDuration () const {
|
|
if (log->connected_date_time == 0)
|
|
return 0;
|
|
return (int)(ms_time(nullptr) - log->connected_date_time);
|
|
}
|
|
|
|
/*
|
|
* Initialize call parameters according to incoming call parameters. This is to avoid to ask later (during reINVITEs) for features that the remote
|
|
* end apparently does not support. This features are: privacy, video...
|
|
*/
|
|
void CallSessionPrivate::initializeParamsAccordingToIncomingCallParams () {
|
|
currentParams->setPrivacy((LinphonePrivacyMask)sal_op_get_privacy(op));
|
|
}
|
|
|
|
void CallSessionPrivate::setState(LinphoneCallState newState, const string &message) {
|
|
L_Q(CallSession);
|
|
if (state != newState){
|
|
prevState = state;
|
|
|
|
/* Make sanity checks with call state changes. Any bad transition can result in unpredictable results
|
|
or irrecoverable errors in the application. */
|
|
if ((state == LinphoneCallEnd) || (state == LinphoneCallError)) {
|
|
if (newState != LinphoneCallReleased) {
|
|
lFatal() << "Abnormal call resurection from " << linphone_call_state_to_string(state) <<
|
|
" to " << linphone_call_state_to_string(newState) << " , aborting";
|
|
return;
|
|
}
|
|
} else if ((newState == LinphoneCallReleased) && (prevState != LinphoneCallError) && (prevState != LinphoneCallEnd)) {
|
|
lFatal() << "Attempt to move CallSession [" << q << "] to Released state while it was not previously in Error or End state, aborting";
|
|
return;
|
|
}
|
|
lInfo() << "CallSession [" << q << "] moving from state " << linphone_call_state_to_string(state) << " to " << linphone_call_state_to_string(newState);
|
|
|
|
if (newState != LinphoneCallRefered) {
|
|
/* LinphoneCallRefered is rather an event, not a state.
|
|
Indeed it does not change the state of the call (still paused or running). */
|
|
state = newState;
|
|
}
|
|
|
|
switch (newState) {
|
|
case LinphoneCallOutgoingInit:
|
|
case LinphoneCallIncomingReceived:
|
|
#ifdef __ANDROID__
|
|
lInfo() << "CallSession [" << q << "] acquires both wifi and multicast lock";
|
|
linphone_core_wifi_lock_acquire(core);
|
|
linphone_core_multicast_lock_acquire(core); /* Does no affect battery more than regular rtp traffic */
|
|
#endif
|
|
break;
|
|
case LinphoneCallEnd:
|
|
case LinphoneCallError:
|
|
switch (linphone_error_info_get_reason(q->getErrorInfo())) {
|
|
case LinphoneReasonDeclined:
|
|
log->status = LinphoneCallDeclined;
|
|
break;
|
|
case LinphoneReasonNotAnswered:
|
|
if (log->dir == LinphoneCallIncoming)
|
|
log->status = LinphoneCallMissed;
|
|
break;
|
|
case LinphoneReasonNone:
|
|
if (log->dir == LinphoneCallIncoming) {
|
|
if (ei) {
|
|
int code = linphone_error_info_get_protocol_code(ei);
|
|
if ((code >= 200) && (code < 300))
|
|
log->status = LinphoneCallAcceptedElsewhere;
|
|
}
|
|
}
|
|
break;
|
|
case LinphoneReasonDoNotDisturb:
|
|
if (log->dir == LinphoneCallIncoming) {
|
|
if (ei) {
|
|
int code = linphone_error_info_get_protocol_code(ei);
|
|
if ((code >= 600) && (code < 700))
|
|
log->status = LinphoneCallDeclinedElsewhere;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
setTerminated();
|
|
break;
|
|
case LinphoneCallConnected:
|
|
log->status = LinphoneCallSuccess;
|
|
log->connected_date_time = ms_time(nullptr);
|
|
break;
|
|
case LinphoneCallReleased:
|
|
#ifdef __ANDROID__
|
|
lInfo() << "CallSession [" << q << "] releases wifi/multicast lock";
|
|
linphone_core_wifi_lock_release(core);
|
|
linphone_core_multicast_lock_release(core);
|
|
#endif
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (newState != LinphoneCallStreamsRunning) {
|
|
#if 0 // TODO
|
|
if (call->dtmfs_timer!=NULL){
|
|
/*cancelling DTMF sequence, if any*/
|
|
linphone_call_cancel_dtmfs(call);
|
|
}
|
|
#endif
|
|
}
|
|
if (message.empty()) {
|
|
lError() << "You must fill a reason when changing call state (from " <<
|
|
linphone_call_state_to_string(prevState) << " to " << linphone_call_state_to_string(state) << ")";
|
|
}
|
|
if (listener)
|
|
listener->onCallSessionStateChanged(*q, state, message);
|
|
if (newState == LinphoneCallReleased)
|
|
setReleased(); /* Shall be performed after app notification */
|
|
}
|
|
}
|
|
|
|
bool CallSessionPrivate::startPing () {
|
|
if (core->sip_conf.ping_with_options) {
|
|
/* Defer the start of the call after the OPTIONS ping for outgoing call or
|
|
* send an option request back to the caller so that we get a chance to discover our nat'd address
|
|
* before answering for incoming call */
|
|
pingReplied = false;
|
|
pingOp = sal_op_new(core->sal);
|
|
if (direction == LinphoneCallIncoming) {
|
|
const char *from = sal_op_get_from(pingOp);
|
|
const char *to = sal_op_get_to(pingOp);
|
|
linphone_configure_op(core, pingOp, log->from, nullptr, false);
|
|
sal_op_set_route(pingOp, sal_op_get_network_origin(op));
|
|
sal_ping(pingOp, from, to);
|
|
} else if (direction == LinphoneCallOutgoing) {
|
|
char *from = linphone_address_as_string(log->from);
|
|
char *to = linphone_address_as_string(log->to);
|
|
sal_ping(pingOp, from, to);
|
|
ms_free(from);
|
|
ms_free(to);
|
|
}
|
|
sal_op_set_user_pointer(pingOp, this);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void CallSessionPrivate::abort (const string &errorMsg) {
|
|
sal_call_terminate(op);
|
|
setState(LinphoneCallError, errorMsg);
|
|
}
|
|
|
|
void CallSessionPrivate::accepted () {
|
|
/* Immediately notify the connected state, even if errors occur after */
|
|
switch (state) {
|
|
case LinphoneCallOutgoingProgress:
|
|
case LinphoneCallOutgoingRinging:
|
|
case LinphoneCallOutgoingEarlyMedia:
|
|
/* Immediately notify the connected state */
|
|
setState(LinphoneCallConnected, "Connected");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
currentParams->setPrivacy((LinphonePrivacyMask)sal_op_get_privacy(op));
|
|
}
|
|
|
|
void CallSessionPrivate::ackBeingSent (LinphoneHeaders *headers) {
|
|
L_Q(CallSession);
|
|
if (listener)
|
|
listener->onAckBeingSent(*q, headers);
|
|
}
|
|
|
|
void CallSessionPrivate::ackReceived (LinphoneHeaders *headers) {
|
|
L_Q(CallSession);
|
|
if (listener)
|
|
listener->onAckReceived(*q, headers);
|
|
}
|
|
|
|
bool CallSessionPrivate::failure () {
|
|
L_Q(CallSession);
|
|
const SalErrorInfo *ei = sal_op_get_error_info(op);
|
|
switch (ei->reason) {
|
|
case SalReasonRedirect:
|
|
if ((state == LinphoneCallOutgoingInit) || (state == LinphoneCallOutgoingProgress)
|
|
|| (state == LinphoneCallOutgoingRinging) /* Push notification case */ || (state == LinphoneCallOutgoingEarlyMedia)) {
|
|
const SalAddress *redirectionTo = sal_op_get_remote_contact_address(op);
|
|
if (redirectionTo) {
|
|
char *url = sal_address_as_string(redirectionTo);
|
|
lWarning() << "Redirecting CallSession [" << q << "] to " << url;
|
|
if (log->to)
|
|
linphone_address_unref(log->to);
|
|
log->to = linphone_address_new(url);
|
|
ms_free(url);
|
|
#if 0
|
|
linphone_call_restart_invite(call);
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Some call errors are not fatal */
|
|
switch (state) {
|
|
case LinphoneCallUpdating:
|
|
case LinphoneCallPausing:
|
|
case LinphoneCallResuming:
|
|
if (ei->reason != SalReasonNoMatch) {
|
|
lInfo() << "Call error on state [" << linphone_call_state_to_string(state) << "], restoring previous state [" << linphone_call_state_to_string(prevState) << "]";
|
|
setState(prevState, ei->full_string);
|
|
return true;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ((state != LinphoneCallEnd) && (state != LinphoneCallError)) {
|
|
if (ei->reason == SalReasonDeclined)
|
|
setState(LinphoneCallEnd, "Call declined");
|
|
else {
|
|
if (linphone_call_state_is_early(state))
|
|
setState(LinphoneCallError, ei->full_string);
|
|
else
|
|
setState(LinphoneCallEnd, ei->full_string);
|
|
}
|
|
#if 0 // TODO: handle in Call class
|
|
if (ei->reason != SalReasonNone)
|
|
linphone_core_play_call_error_tone(core, linphone_reason_from_sal(ei->reason));
|
|
#endif
|
|
}
|
|
#if 0
|
|
LinphoneCall *referer=call->referer;
|
|
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");
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void CallSessionPrivate::pingReply () {
|
|
L_Q(CallSession);
|
|
if (state == LinphoneCallOutgoingInit) {
|
|
pingReplied = true;
|
|
if (isReadyForInvite())
|
|
q->startInvite(nullptr, "");
|
|
}
|
|
}
|
|
|
|
void CallSessionPrivate::remoteRinging () {
|
|
L_Q(CallSession);
|
|
/* Set privacy */
|
|
q->getCurrentParams()->setPrivacy((LinphonePrivacyMask)sal_op_get_privacy(op));
|
|
#if 0
|
|
if (lc->ringstream == NULL) start_remote_ring(lc, call);
|
|
#endif
|
|
lInfo() << "Remote ringing...";
|
|
setState(LinphoneCallOutgoingRinging, "Remote ringing");
|
|
}
|
|
|
|
void CallSessionPrivate::terminated () {
|
|
switch (state) {
|
|
case LinphoneCallEnd:
|
|
case LinphoneCallError:
|
|
lWarning() << "terminated: already terminated, ignoring";
|
|
return;
|
|
case LinphoneCallIncomingReceived:
|
|
case LinphoneCallIncomingEarlyMedia:
|
|
if (!sal_op_get_reason_error_info(op)->protocol || strcmp(sal_op_get_reason_error_info(op)->protocol, "") == 0) {
|
|
linphone_error_info_set(ei, nullptr, LinphoneReasonNotAnswered, 0, "Incoming call cancelled", nullptr);
|
|
nonOpError = true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#if 0
|
|
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);
|
|
}
|
|
#endif
|
|
setState(LinphoneCallEnd, "Call ended");
|
|
}
|
|
|
|
void CallSessionPrivate::updated (bool isUpdate) {
|
|
deferUpdate = lp_config_get_int(linphone_core_get_config(core), "sip", "defer_update_default", FALSE);
|
|
SalErrorInfo sei;
|
|
memset(&sei, 0, sizeof(sei));
|
|
switch (state) {
|
|
case LinphoneCallPausedByRemote:
|
|
updatedByRemote();
|
|
break;
|
|
/* SIP UPDATE CASE */
|
|
case LinphoneCallOutgoingRinging:
|
|
case LinphoneCallOutgoingEarlyMedia:
|
|
case LinphoneCallIncomingEarlyMedia:
|
|
if (isUpdate) {
|
|
setState(LinphoneCallEarlyUpdatedByRemote, "EarlyUpdatedByRemote");
|
|
acceptUpdate(nullptr, prevState, linphone_call_state_to_string(prevState));
|
|
}
|
|
break;
|
|
case LinphoneCallStreamsRunning:
|
|
case LinphoneCallConnected:
|
|
case LinphoneCallUpdatedByRemote: /* Can happen on UAC connectivity loss */
|
|
updatedByRemote();
|
|
break;
|
|
case LinphoneCallPaused:
|
|
/* We'll remain in pause state but accept the offer anyway according to default parameters */
|
|
acceptUpdate(nullptr, state, linphone_call_state_to_string(state));
|
|
break;
|
|
case LinphoneCallUpdating:
|
|
case LinphoneCallPausing:
|
|
case LinphoneCallResuming:
|
|
sal_error_info_set(&sei, SalReasonInternalError, "SIP", 0, nullptr, nullptr);
|
|
sal_call_decline_with_error_info(op, &sei, nullptr);
|
|
BCTBX_NO_BREAK; /* no break */
|
|
case LinphoneCallIdle:
|
|
case LinphoneCallOutgoingInit:
|
|
case LinphoneCallEnd:
|
|
case LinphoneCallIncomingReceived:
|
|
case LinphoneCallOutgoingProgress:
|
|
case LinphoneCallRefered:
|
|
case LinphoneCallError:
|
|
case LinphoneCallReleased:
|
|
case LinphoneCallEarlyUpdatedByRemote:
|
|
case LinphoneCallEarlyUpdating:
|
|
lWarning() << "Receiving reINVITE or UPDATE while in state [" << linphone_call_state_to_string(state) << "], should not happen";
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CallSessionPrivate::updatedByRemote () {
|
|
L_Q(CallSession);
|
|
setState(LinphoneCallUpdatedByRemote,"Call updated by remote");
|
|
if (deferUpdate) {
|
|
if (state == LinphoneCallUpdatedByRemote)
|
|
lInfo() << "CallSession [" << q << "]: UpdatedByRemoted was signaled but defered. LinphoneCore expects the application to call linphone_core_accept_call_update() later.";
|
|
} else {
|
|
if (state == LinphoneCallUpdatedByRemote)
|
|
q->acceptUpdate(nullptr);
|
|
else {
|
|
/* Otherwise it means that the app responded by linphone_core_accept_call_update
|
|
* within the callback, so job is already done. */
|
|
}
|
|
}
|
|
}
|
|
|
|
void CallSessionPrivate::updating (bool isUpdate) {
|
|
updated(isUpdate);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void CallSessionPrivate::accept (const CallSessionParams *params) {
|
|
L_Q(CallSession);
|
|
/* Try to be best-effort in giving real local or routable contact address */
|
|
setContactOp();
|
|
if (params) {
|
|
this->params = new CallSessionParams(*params);
|
|
sal_op_set_sent_custom_header(op, this->params->getPrivate()->getCustomHeaders());
|
|
}
|
|
|
|
sal_call_accept(op);
|
|
if (listener)
|
|
listener->onSetCurrentSession(*q);
|
|
setState(LinphoneCallConnected, "Connected");
|
|
}
|
|
|
|
LinphoneStatus CallSessionPrivate::acceptUpdate (const CallSessionParams *csp, LinphoneCallState nextState, const string &stateInfo) {
|
|
return startAcceptUpdate(nextState, stateInfo);
|
|
}
|
|
|
|
LinphoneStatus CallSessionPrivate::checkForAcceptation () const {
|
|
L_Q(const CallSession);
|
|
switch (state) {
|
|
case LinphoneCallIncomingReceived:
|
|
case LinphoneCallIncomingEarlyMedia:
|
|
break;
|
|
default:
|
|
lError() << "checkForAcceptation() CallSession [" << q << "] is in state [" << linphone_call_state_to_string(state) << "], operation not permitted";
|
|
return -1;
|
|
}
|
|
if (listener)
|
|
listener->onCheckForAcceptation(*q);
|
|
|
|
/* Check if this call is supposed to replace an already running one */
|
|
SalOp *replaced = sal_call_get_replaces(op);
|
|
if (replaced) {
|
|
CallSession *session = reinterpret_cast<CallSession *>(sal_op_get_user_pointer(replaced));
|
|
if (session) {
|
|
lInfo() << "CallSession " << q << " replaces CallSession " << session << ". This last one is going to be terminated automatically";
|
|
session->terminate();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void CallSessionPrivate::handleIncomingReceivedStateInIncomingNotification () {
|
|
L_Q(CallSession);
|
|
/* Try to be best-effort in giving real local or routable contact address for 100Rel case */
|
|
setContactOp();
|
|
sal_call_notify_ringing(op, false);
|
|
if (sal_call_get_replaces(op) && lp_config_get_int(linphone_core_get_config(core), "sip", "auto_answer_replacing_calls", 1))
|
|
q->accept();
|
|
}
|
|
|
|
bool CallSessionPrivate::isReadyForInvite () const {
|
|
bool pingReady = false;
|
|
if (pingOp) {
|
|
if (pingReplied)
|
|
pingReady = true;
|
|
} else
|
|
pingReady = true;
|
|
return pingReady;
|
|
}
|
|
|
|
bool CallSessionPrivate::isUpdateAllowed (LinphoneCallState &nextState) const {
|
|
switch (state) {
|
|
case LinphoneCallIncomingReceived:
|
|
case LinphoneCallIncomingEarlyMedia:
|
|
case LinphoneCallOutgoingRinging:
|
|
case LinphoneCallOutgoingEarlyMedia:
|
|
nextState = LinphoneCallEarlyUpdating;
|
|
break;
|
|
case LinphoneCallStreamsRunning:
|
|
case LinphoneCallPausedByRemote:
|
|
case LinphoneCallUpdatedByRemote:
|
|
nextState = LinphoneCallUpdating;
|
|
break;
|
|
case LinphoneCallPaused:
|
|
nextState = LinphoneCallPausing;
|
|
break;
|
|
case LinphoneCallOutgoingProgress:
|
|
case LinphoneCallPausing:
|
|
case LinphoneCallResuming:
|
|
case LinphoneCallUpdating:
|
|
nextState = state;
|
|
break;
|
|
default:
|
|
lError() << "Update is not allowed in [" << linphone_call_state_to_string(state) << "] state";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Called internally when reaching the Released state, to perform cleanups to break circular references.
|
|
**/
|
|
void CallSessionPrivate::setReleased () {
|
|
L_Q(CallSession);
|
|
if (op) {
|
|
/* Transfer the last error so that it can be obtained even in Released state */
|
|
if (!nonOpError)
|
|
linphone_error_info_from_sal_op(ei, op);
|
|
/* So that we cannot have anymore upcalls for SAL concerning this call */
|
|
sal_op_release(op);
|
|
op = nullptr;
|
|
}
|
|
#if 0
|
|
/* It is necessary to reset pointers to other call to prevent circular references that would result in memory never freed */
|
|
if (call->referer){
|
|
linphone_call_unref(call->referer);
|
|
call->referer=NULL;
|
|
}
|
|
if (call->transfer_target){
|
|
linphone_call_unref(call->transfer_target);
|
|
call->transfer_target=NULL;
|
|
}
|
|
if (call->chat_room){
|
|
linphone_chat_room_unref(call->chat_room);
|
|
call->chat_room = NULL;
|
|
}
|
|
#endif
|
|
if (listener)
|
|
listener->onCallSessionSetReleased(*q);
|
|
}
|
|
|
|
/* This method is called internally to get rid of a call that was notified to the application,
|
|
* because it reached the end or error state. It performs the following tasks:
|
|
* - remove the call from the internal list of calls
|
|
* - update the call logs accordingly
|
|
*/
|
|
void CallSessionPrivate::setTerminated() {
|
|
L_Q(CallSession);
|
|
completeLog();
|
|
if (listener)
|
|
listener->onCallSessionSetTerminated(*q);
|
|
}
|
|
|
|
LinphoneStatus CallSessionPrivate::startAcceptUpdate (LinphoneCallState nextState, const string &stateInfo) {
|
|
sal_call_accept(op);
|
|
setState(nextState, stateInfo);
|
|
return 0;
|
|
}
|
|
|
|
LinphoneStatus CallSessionPrivate::startUpdate () {
|
|
L_Q(CallSession);
|
|
string subject;
|
|
if (q->getParams()->getPrivate()->getInConference())
|
|
subject = "Conference";
|
|
else if (q->getParams()->getPrivate()->getInternalCallUpdate())
|
|
subject = "ICE processing concluded";
|
|
else if (q->getParams()->getPrivate()->getNoUserConsent())
|
|
subject = "Refreshing";
|
|
else
|
|
subject = "Media change";
|
|
if (destProxy && destProxy->op) {
|
|
/* Give a chance to update the contact address if connectivity has changed */
|
|
sal_op_set_contact_address(op, sal_op_get_contact_address(destProxy->op));
|
|
} else
|
|
sal_op_set_contact_address(op, nullptr);
|
|
return sal_call_update(op, subject.c_str(), q->getParams()->getPrivate()->getNoUserConsent());
|
|
}
|
|
|
|
void CallSessionPrivate::terminate () {
|
|
if ((state == LinphoneCallIncomingReceived) && (linphone_error_info_get_reason(ei) != LinphoneReasonNotAnswered)) {
|
|
linphone_error_info_set_reason(ei, LinphoneReasonDeclined);
|
|
nonOpError = true;
|
|
}
|
|
setState(LinphoneCallEnd, "Call terminated");
|
|
}
|
|
|
|
void CallSessionPrivate::updateCurrentParams () const {}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void CallSessionPrivate::setContactOp () {
|
|
SalAddress *salAddress = nullptr;
|
|
LinphoneAddress *contact = getFixedContact();
|
|
if (contact) {
|
|
salAddress = const_cast<SalAddress *>(L_GET_PRIVATE_FROM_C_OBJECT(contact)->getInternalAddress());
|
|
sal_address_ref(salAddress);
|
|
linphone_address_unref(contact);
|
|
}
|
|
sal_op_set_and_clean_contact_address(op, salAddress);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void CallSessionPrivate::completeLog () {
|
|
log->duration = computeDuration(); /* Store duration since connected */
|
|
log->error_info = linphone_error_info_ref(ei);
|
|
if (log->status == LinphoneCallMissed)
|
|
core->missed_calls++;
|
|
linphone_core_report_call_log(core, log);
|
|
}
|
|
|
|
void CallSessionPrivate::createOpTo (const LinphoneAddress *to) {
|
|
L_Q(CallSession);
|
|
if (op)
|
|
sal_op_release(op);
|
|
op = sal_op_new(core->sal);
|
|
sal_op_set_user_pointer(op, q);
|
|
#if 0
|
|
if (linphone_call_params_get_referer(call->params))
|
|
sal_call_set_referer(call->op,linphone_call_params_get_referer(call->params)->op);
|
|
#endif
|
|
linphone_configure_op(core, op, to, q->getParams()->getPrivate()->getCustomHeaders(), false);
|
|
if (q->getParams()->getPrivacy() != LinphonePrivacyDefault)
|
|
sal_op_set_privacy(op, (SalPrivacyMask)q->getParams()->getPrivacy());
|
|
/* else privacy might be set by proxy */
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
LinphoneAddress * CallSessionPrivate::getFixedContact () const {
|
|
LinphoneAddress *result = nullptr;
|
|
if (op && sal_op_get_contact_address(op)) {
|
|
/* If already choosed, don't change it */
|
|
return nullptr;
|
|
} else if (pingOp && sal_op_get_contact_address(pingOp)) {
|
|
/* If the ping OPTIONS request succeeded use the contact guessed from the received, rport */
|
|
lInfo() << "Contact has been fixed using OPTIONS";
|
|
char *addr = sal_address_as_string(sal_op_get_contact_address(pingOp));
|
|
result = linphone_address_new(addr);
|
|
ms_free(addr);
|
|
} else if (destProxy && destProxy->op && sal_op_get_contact_address(destProxy->op)) {
|
|
/* If using a proxy, use the contact address as guessed with the REGISTERs */
|
|
lInfo() << "Contact has been fixed using proxy";
|
|
char *addr = sal_address_as_string(sal_op_get_contact_address(destProxy->op));
|
|
result = linphone_address_new(addr);
|
|
ms_free(addr);
|
|
} else {
|
|
result = linphone_core_get_primary_contact_parsed(core);
|
|
if (result) {
|
|
/* Otherwise use supplied localip */
|
|
linphone_address_set_domain(result, nullptr /* localip */);
|
|
linphone_address_set_port(result, -1 /* linphone_core_get_sip_port(core) */);
|
|
lInfo() << "Contact has not been fixed, stack will do";
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// =============================================================================
|
|
|
|
CallSession::CallSession (const Conference &conference, const CallSessionParams *params, CallSessionListener *listener)
|
|
: Object(*new CallSessionPrivate(conference, params, listener)) {
|
|
lInfo() << "New CallSession [" << this << "] initialized (LinphoneCore version: " << linphone_core_get_version() << ")";
|
|
}
|
|
|
|
CallSession::CallSession (CallSessionPrivate &p) : Object(p) {}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
LinphoneStatus CallSession::accept (const CallSessionParams *csp) {
|
|
L_D(CallSession);
|
|
LinphoneStatus result = d->checkForAcceptation();
|
|
if (result < 0) return result;
|
|
d->accept(csp);
|
|
return 0;
|
|
}
|
|
|
|
LinphoneStatus CallSession::acceptUpdate (const CallSessionParams *csp) {
|
|
L_D(CallSession);
|
|
if (d->state != LinphoneCallUpdatedByRemote) {
|
|
lError() << "CallSession::acceptUpdate(): invalid state " << linphone_call_state_to_string(d->state) << " to call this method";
|
|
return -1;
|
|
}
|
|
return d->acceptUpdate(csp, d->prevState, linphone_call_state_to_string(d->prevState));
|
|
}
|
|
|
|
void CallSession::configure (LinphoneCallDir direction, LinphoneProxyConfig *cfg, SalOp *op, const Address &from, const Address &to) {
|
|
L_D(CallSession);
|
|
d->direction = direction;
|
|
d->destProxy = cfg;
|
|
LinphoneAddress *fromAddr = linphone_address_new(from.asString().c_str());
|
|
LinphoneAddress *toAddr = linphone_address_new(to.asString().c_str());
|
|
if (!d->destProxy) {
|
|
/* Try to define the destination proxy if it has not already been done to have a correct contact field in the SIP messages */
|
|
d->destProxy = linphone_core_lookup_known_proxy(d->core, toAddr);
|
|
}
|
|
d->log = linphone_call_log_new(direction, fromAddr, toAddr);
|
|
|
|
if (op) {
|
|
/* We already have an op for incoming calls */
|
|
d->op = op;
|
|
sal_op_set_user_pointer(d->op, this);
|
|
sal_op_cnx_ip_to_0000_if_sendonly_enable(op, !!lp_config_get_default_int(linphone_core_get_config(d->core),
|
|
"sip", "cnx_ip_to_0000_if_sendonly_enabled", 0));
|
|
d->log->call_id = ms_strdup(sal_op_get_call_id(op)); /* Must be known at that time */
|
|
}
|
|
|
|
if (direction == LinphoneCallOutgoing) {
|
|
d->startPing();
|
|
} else if (direction == LinphoneCallIncoming) {
|
|
d->params = new CallSessionParams();
|
|
d->params->initDefault(d->core);
|
|
}
|
|
}
|
|
|
|
LinphoneStatus CallSession::decline (LinphoneReason reason) {
|
|
LinphoneErrorInfo *ei = linphone_error_info_new();
|
|
linphone_error_info_set(ei, "SIP", reason, linphone_reason_to_error_code(reason), nullptr, nullptr);
|
|
LinphoneStatus status = decline(ei);
|
|
linphone_error_info_unref(ei);
|
|
return status;
|
|
}
|
|
|
|
LinphoneStatus CallSession::decline (const LinphoneErrorInfo *ei) {
|
|
L_D(CallSession);
|
|
SalErrorInfo sei;
|
|
SalErrorInfo sub_sei;
|
|
memset(&sei, 0, sizeof(sei));
|
|
memset(&sub_sei, 0, sizeof(sub_sei));
|
|
sei.sub_sei = &sub_sei;
|
|
if ((d->state != LinphoneCallIncomingReceived) && (d->state != LinphoneCallIncomingEarlyMedia)) {
|
|
lError() << "Cannot decline a CallSession that is in state " << linphone_call_state_to_string(d->state);
|
|
return -1;
|
|
}
|
|
if (ei) {
|
|
linphone_error_info_to_sal(ei, &sei);
|
|
sal_call_decline_with_error_info(d->op, &sei , nullptr);
|
|
} else
|
|
sal_call_decline(d->op, SalReasonDeclined, nullptr);
|
|
sal_error_info_reset(&sei);
|
|
sal_error_info_reset(&sub_sei);
|
|
d->terminate();
|
|
return 0;
|
|
}
|
|
|
|
void CallSession::initiateIncoming () {}
|
|
|
|
bool CallSession::initiateOutgoing () {
|
|
L_D(CallSession);
|
|
bool defer = false;
|
|
d->setState(LinphoneCallOutgoingInit, "Starting outgoing call");
|
|
d->log->start_date_time = ms_time(nullptr);
|
|
if (!d->destProxy)
|
|
defer = d->startPing();
|
|
if (d->direction == LinphoneCallOutgoing) {
|
|
d->createOpTo(d->log->to);
|
|
}
|
|
return defer;
|
|
}
|
|
|
|
void CallSession::iterate (time_t currentRealTime, bool oneSecondElapsed) {
|
|
L_D(CallSession);
|
|
int elapsed = (int)(currentRealTime - d->log->start_date_time);
|
|
if ((d->state == LinphoneCallOutgoingInit) && (elapsed >= d->core->sip_conf.delayed_timeout)) {
|
|
/* Start the call even if the OPTIONS reply did not arrive */
|
|
startInvite(nullptr, "");
|
|
}
|
|
if ((d->state == LinphoneCallIncomingReceived) || (d->state == LinphoneCallIncomingEarlyMedia)) {
|
|
if (oneSecondElapsed)
|
|
lInfo() << "Incoming call ringing for " << elapsed << " seconds";
|
|
if (elapsed > d->core->sip_conf.inc_timeout) {
|
|
lInfo() << "Incoming call timeout (" << d->core->sip_conf.inc_timeout << ")";
|
|
#if 0
|
|
LinphoneReason declineReason = (core->current_call != call) ? LinphoneReasonBusy : LinphoneReasonDeclined;
|
|
#endif
|
|
d->log->status = LinphoneCallMissed;
|
|
#if 0
|
|
call->non_op_error = TRUE;
|
|
linphone_error_info_set(call->ei, NULL, decline_reason, linphone_reason_to_error_code(decline_reason), "Not answered", NULL);
|
|
linphone_call_decline(call, decline_reason);
|
|
#endif
|
|
}
|
|
}
|
|
if ((d->core->sip_conf.in_call_timeout > 0) && (d->log->connected_date_time != 0)
|
|
&& ((currentRealTime - d->log->connected_date_time) > d->core->sip_conf.in_call_timeout)) {
|
|
lInfo() << "In call timeout (" << d->core->sip_conf.in_call_timeout << ")";
|
|
terminate();
|
|
}
|
|
}
|
|
|
|
void CallSession::startIncomingNotification () {
|
|
L_D(CallSession);
|
|
if (d->listener)
|
|
d->listener->onCallSessionAccepted(*this);
|
|
/* Prevent the CallSession from being destroyed while we are notifying, if the user declines within the state callback */
|
|
shared_ptr<CallSession> ref = static_pointer_cast<CallSession>(shared_from_this());
|
|
#if 0
|
|
call->bg_task_id=sal_begin_background_task("liblinphone call notification", NULL, NULL);
|
|
#endif
|
|
if (d->deferIncomingNotification) {
|
|
lInfo() << "Defer incoming notification";
|
|
return;
|
|
}
|
|
|
|
if (d->listener)
|
|
d->listener->onIncomingCallSessionStarted(*this);
|
|
|
|
d->setState(LinphoneCallIncomingReceived, "Incoming CallSession");
|
|
|
|
/* From now on, the application is aware of the call and supposed to take background task or already submitted notification to the user.
|
|
* We can then drop our background task. */
|
|
#if 0
|
|
if (call->bg_task_id!=0) {
|
|
sal_end_background_task(call->bg_task_id);
|
|
call->bg_task_id=0;
|
|
}
|
|
#endif
|
|
|
|
if (d->state == LinphoneCallIncomingReceived) {
|
|
d->handleIncomingReceivedStateInIncomingNotification();
|
|
}
|
|
}
|
|
|
|
int CallSession::startInvite (const Address *destination, const string &subject) {
|
|
L_D(CallSession);
|
|
/* Try to be best-effort in giving real local or routable contact address */
|
|
d->setContactOp();
|
|
string destinationStr;
|
|
char *realUrl = nullptr;
|
|
if (destination)
|
|
destinationStr = destination->asString();
|
|
else {
|
|
realUrl = linphone_address_as_string(d->log->to);
|
|
destinationStr = realUrl;
|
|
ms_free(realUrl);
|
|
}
|
|
char *from = linphone_address_as_string(d->log->from);
|
|
/* Take a ref because sal_call() may destroy the CallSession if no SIP transport is available */
|
|
shared_ptr<CallSession> ref = static_pointer_cast<CallSession>(shared_from_this());
|
|
int result = sal_call(d->op, from, destinationStr.c_str(), subject.empty() ? nullptr : subject.c_str());
|
|
ms_free(from);
|
|
if (result < 0) {
|
|
if ((d->state != LinphoneCallError) && (d->state != LinphoneCallReleased)) {
|
|
/* sal_call() may invoke call_failure() and call_released() SAL callbacks synchronously,
|
|
in which case there is no need to perform a state change here. */
|
|
d->setState(LinphoneCallError, "Call failed");
|
|
}
|
|
} else {
|
|
d->log->call_id = ms_strdup(sal_op_get_call_id(d->op)); /* Must be known at that time */
|
|
d->setState(LinphoneCallOutgoingProgress, "Outgoing call in progress");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
LinphoneStatus CallSession::terminate (const LinphoneErrorInfo *ei) {
|
|
L_D(CallSession);
|
|
lInfo() << "Terminate CallSession [" << this << "] which is currently in state [" << linphone_call_state_to_string(d->state) << "]";
|
|
SalErrorInfo sei;
|
|
memset(&sei, 0, sizeof(sei));
|
|
switch (d->state) {
|
|
case LinphoneCallReleased:
|
|
case LinphoneCallEnd:
|
|
case LinphoneCallError:
|
|
lWarning() << "No need to terminate CallSession [" << this << "] in state [" << linphone_call_state_to_string(d->state) << "]";
|
|
return -1;
|
|
case LinphoneCallIncomingReceived:
|
|
case LinphoneCallIncomingEarlyMedia:
|
|
return decline(ei);
|
|
case LinphoneCallOutgoingInit:
|
|
/* In state OutgoingInit, op has to be destroyed */
|
|
sal_op_release(d->op);
|
|
d->op = nullptr;
|
|
break;
|
|
default:
|
|
if (ei) {
|
|
linphone_error_info_to_sal(ei, &sei);
|
|
sal_call_terminate_with_error(d->op, &sei);
|
|
sal_error_info_reset(&sei);
|
|
} else
|
|
sal_call_terminate(d->op);
|
|
break;
|
|
}
|
|
|
|
d->terminate();
|
|
return 0;
|
|
}
|
|
|
|
LinphoneStatus CallSession::update (const CallSessionParams *csp) {
|
|
L_D(CallSession);
|
|
LinphoneCallState nextState;
|
|
LinphoneCallState initialState = d->state;
|
|
if (!d->isUpdateAllowed(nextState))
|
|
return -1;
|
|
if (d->currentParams == csp)
|
|
lWarning() << "CallSession::update() is given the current params, this is probably not what you intend to do!";
|
|
LinphoneStatus result = d->startUpdate();
|
|
if (result && (d->state != initialState)) {
|
|
/* Restore initial state */
|
|
d->setState(initialState, "Restore initial state");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
LinphoneCallDir CallSession::getDirection () const {
|
|
L_D(const CallSession);
|
|
return d->direction;
|
|
}
|
|
|
|
int CallSession::getDuration () const {
|
|
L_D(const CallSession);
|
|
switch (d->state) {
|
|
case LinphoneCallEnd:
|
|
case LinphoneCallError:
|
|
case LinphoneCallReleased:
|
|
return d->log->duration;
|
|
default:
|
|
return d->computeDuration();
|
|
}
|
|
}
|
|
|
|
const LinphoneErrorInfo * CallSession::getErrorInfo () const {
|
|
L_D(const CallSession);
|
|
if (!d->nonOpError)
|
|
linphone_error_info_from_sal_op(d->ei, d->op);
|
|
return d->ei;
|
|
}
|
|
|
|
LinphoneCallLog * CallSession::getLog () const {
|
|
L_D(const CallSession);
|
|
return d->log;
|
|
}
|
|
|
|
LinphoneReason CallSession::getReason () const {
|
|
return linphone_error_info_get_reason(getErrorInfo());
|
|
}
|
|
|
|
const Address& CallSession::getRemoteAddress () const {
|
|
L_D(const CallSession);
|
|
return *L_GET_CPP_PTR_FROM_C_OBJECT((d->direction == LinphoneCallIncoming)
|
|
? linphone_call_log_get_from(d->log) : linphone_call_log_get_to(d->log));
|
|
}
|
|
|
|
string CallSession::getRemoteAddressAsString () const {
|
|
return getRemoteAddress().asString();
|
|
}
|
|
|
|
string CallSession::getRemoteContact () const {
|
|
L_D(const CallSession);
|
|
if (d->op) {
|
|
/* sal_op_get_remote_contact preserves header params */
|
|
return sal_op_get_remote_contact(d->op);
|
|
}
|
|
return string();
|
|
}
|
|
|
|
const CallSessionParams * CallSession::getRemoteParams () {
|
|
L_D(CallSession);
|
|
if (d->op){
|
|
const SalCustomHeader *ch = sal_op_get_recv_custom_header(d->op);
|
|
if (ch) {
|
|
/* Instanciate a remote_params only if a SIP message was received before (custom headers indicates this) */
|
|
if (!d->remoteParams)
|
|
d->remoteParams = new CallSessionParams();
|
|
d->remoteParams->getPrivate()->setCustomHeaders(ch);
|
|
}
|
|
return d->remoteParams;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
LinphoneCallState CallSession::getState () const {
|
|
L_D(const CallSession);
|
|
return d->state;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
string CallSession::getRemoteUserAgent () const {
|
|
L_D(const CallSession);
|
|
if (d->op)
|
|
return sal_op_get_remote_ua(d->op);
|
|
return string();
|
|
}
|
|
|
|
CallSessionParams * CallSession::getCurrentParams () const {
|
|
L_D(const CallSession);
|
|
d->updateCurrentParams();
|
|
return d->currentParams;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
const CallSessionParams * CallSession::getParams () const {
|
|
L_D(const CallSession);
|
|
return d->params;
|
|
}
|
|
|
|
LINPHONE_END_NAMESPACE
|