diff --git a/.cproject b/.cproject
index e109f8b5b..f20bc862b 100644
--- a/.cproject
+++ b/.cproject
@@ -28,6 +28,9 @@
+
@@ -36,9 +39,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/coreapi/bellesip_sal/sal_impl.c b/coreapi/bellesip_sal/sal_impl.c
index 04bee6085..47242cdd5 100644
--- a/coreapi/bellesip_sal/sal_impl.c
+++ b/coreapi/bellesip_sal/sal_impl.c
@@ -246,7 +246,9 @@ static void process_request_event(void *ud, const belle_sip_request_event_t *eve
return;
}else {
ms_error("sal process_request_event not implemented yet for method [%s]",belle_sip_request_get_method(req));
- resp=belle_sip_response_create_from_request(req,501);
+ resp=belle_sip_response_create_from_request(req,405);
+ belle_sip_message_add_header(BELLE_SIP_MESSAGE(resp)
+ ,BELLE_SIP_HEADER(belle_sip_header_allow_create("INVITE, CANCEL, ACK, BYE, SUBSCRIBE, NOTIFY, MESSAGE, OPTIONS, INFO")));
belle_sip_provider_send_response(sal->prov,resp);
return;
}
diff --git a/coreapi/bellesip_sal/sal_impl.h b/coreapi/bellesip_sal/sal_impl.h
index f74936b63..1a38ce7dc 100644
--- a/coreapi/bellesip_sal/sal_impl.h
+++ b/coreapi/bellesip_sal/sal_impl.h
@@ -79,6 +79,7 @@ struct SalOp{
belle_sip_listener_callbacks_t callbacks;
belle_sip_client_transaction_t *pending_auth_transaction;
belle_sip_server_transaction_t* pending_server_trans;
+ belle_sip_server_transaction_t* pending_update_server_trans;
belle_sip_client_transaction_t* pending_client_trans;
SalAuthInfo* auth_info;
belle_sip_dialog_t* dialog;
diff --git a/coreapi/bellesip_sal/sal_op_call.c b/coreapi/bellesip_sal/sal_op_call.c
index bc97bd1a9..01422e224 100644
--- a/coreapi/bellesip_sal/sal_op_call.c
+++ b/coreapi/bellesip_sal/sal_op_call.c
@@ -226,6 +226,11 @@ static void call_process_response(void *op_base, const belle_sip_response_event_
call_set_error(op,response);
if (op->dialog==NULL) call_set_released(op);
}
+ } else if ( code >=200
+ && code<300
+ && strcmp("UPDATE",belle_sip_request_get_method(req))==0) {
+ handle_sdp_from_response(op,response);
+ op->base.root->callbacks.call_accepted(op);
}
}
break;
@@ -384,6 +389,16 @@ static int process_sdp_for_invite(SalOp* op,belle_sip_request_t* invite) {
return err;
}
+static void sal_op_reset_descriptions(SalOp *op) {
+ if (op->base.remote_media){
+ sal_media_description_unref(op->base.remote_media);
+ op->base.remote_media=NULL;
+ }
+ if (op->result){
+ sal_media_description_unref(op->result);
+ op->result=NULL;
+ }
+}
static void process_request_event(void *op_base, const belle_sip_request_event_t *event) {
SalOp* op = (SalOp*)op_base;
belle_sip_server_transaction_t* server_transaction=NULL;
@@ -408,6 +423,13 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t
belle_sip_object_ref(op->pending_server_trans);
}
+ if (strcmp("UPDATE",method)==0) {
+ if (op->pending_update_server_trans) belle_sip_object_unref(op->pending_update_server_trans);
+ /*updating pending update transaction*/
+ op->pending_update_server_trans=server_transaction;
+ belle_sip_object_ref(op->pending_update_server_trans);
+ }
+
if (!op->dialog) {
set_or_update_dialog(op,belle_sip_provider_create_dialog(op->base.root->prov,BELLE_SIP_TRANSACTION(op->pending_server_trans)));
ms_message("new incoming call from [%s] to [%s]",sal_op_get_from(op),sal_op_get_to(op));
@@ -455,6 +477,10 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t
} else if (strcmp("PRACK",method)==0) {
resp=sal_op_create_response_from_request(op,req,200);
belle_sip_server_transaction_send_response(server_transaction,resp);
+ } else if (strcmp("UPDATE",method)==0) {
+ sal_op_reset_descriptions(op);
+ if (process_sdp_for_invite(op,req)==0)
+ op->base.root->callbacks.call_updating(op);
} else {
belle_sip_error("Unexpected method [%s] for dialog state BELLE_SIP_DIALOG_EARLY",belle_sip_request_get_method(req));
unsupported_method(server_transaction,req);
@@ -479,10 +505,6 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t
}
}
}
- /*FIXME
- if (op->reinvite){
- op->reinvite=FALSE;
- }*/
op->base.root->callbacks.call_ack(op);
} else if(strcmp("BYE",method)==0) {
resp=sal_op_create_response_from_request(op,req,200);
@@ -492,14 +514,7 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t
/*call end not notified by dialog deletion because transaction can end before dialog*/
} else if(strcmp("INVITE",method)==0) {
/*re-invite*/
- if (op->base.remote_media){
- sal_media_description_unref(op->base.remote_media);
- op->base.remote_media=NULL;
- }
- if (op->result){
- sal_media_description_unref(op->result);
- op->result=NULL;
- }
+ sal_op_reset_descriptions(op);
if (process_sdp_for_invite(op,req)==0)
op->base.root->callbacks.call_updating(op);
} else if (strcmp("INFO",method)==0){
@@ -576,7 +591,7 @@ int sal_call_set_local_media_description(SalOp *op, SalMediaDescription *desc){
static belle_sip_header_allow_t *create_allow(){
belle_sip_header_allow_t* header_allow;
- header_allow = belle_sip_header_allow_create("INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO");
+ header_allow = belle_sip_header_allow_create("INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO, UPDATE");
return header_allow;
}
@@ -684,14 +699,21 @@ int sal_call_notify_ringing(SalOp *op, bool_t early_media){
int sal_call_accept(SalOp*h){
belle_sip_response_t *response;
belle_sip_header_contact_t* contact_header;
+ belle_sip_server_transaction_t* transaction;
- if (!h->pending_server_trans) {
+ /*first check if an UPDATE transaction need to be accepted*/
+ if (h->pending_update_server_trans) {
+ transaction=h->pending_update_server_trans;
+ } else if (h->pending_server_trans) {
+ /*so it must be an invite/re-invite*/
+ transaction=h->pending_server_trans;
+ } else {
ms_error("No transaction to accept for op [%p]",h);
return -1;
}
-
+ ms_message("Accepting server transaction [%p] on op [%p]", transaction, h);
/* sends a 200 OK */
- response = sal_op_create_response_from_request(h,belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(h->pending_server_trans)),200);
+ response = sal_op_create_response_from_request(h,belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(transaction)),200);
if (response==NULL){
ms_error("Fail to build answer for call");
@@ -712,7 +734,11 @@ int sal_call_accept(SalOp*h){
handle_offer_answer_response(h,response);
- belle_sip_server_transaction_send_response(h->pending_server_trans,response);
+ belle_sip_server_transaction_send_response(transaction,response);
+ if (h->pending_update_server_trans) {
+ belle_sip_object_unref(h->pending_update_server_trans);
+ h->pending_update_server_trans=NULL;
+ }
return 0;
}
@@ -738,11 +764,22 @@ int sal_call_decline(SalOp *op, SalReason reason, const char *redirection /*opti
}
int sal_call_update(SalOp *op, const char *subject){
- belle_sip_request_t *reinvite=belle_sip_dialog_create_request(op->dialog,"INVITE");
- if (reinvite){
- belle_sip_message_add_header(BELLE_SIP_MESSAGE(reinvite),belle_sip_header_create( "Subject", subject));
- sal_op_fill_invite(op, reinvite);
- return sal_op_send_request(op,reinvite);
+
+ belle_sip_request_t *update;
+ belle_sip_dialog_state_t state=belle_sip_dialog_get_state(op->dialog);
+ /*check for dialog state*/
+ if ( state == BELLE_SIP_DIALOG_CONFIRMED) {
+ update=belle_sip_dialog_create_request(op->dialog,"INVITE");
+ } else if (state == BELLE_SIP_DIALOG_EARLY) {
+ update=belle_sip_dialog_create_request(op->dialog,"UPDATE");
+ } else {
+ ms_error("Cannot update op [%p] with dialog [%p] in state [%s]",op, op->dialog,belle_sip_dialog_state_to_string(state));
+ return -1;
+ }
+ if (update){
+ belle_sip_message_add_header(BELLE_SIP_MESSAGE(update),belle_sip_header_create( "Subject", subject));
+ sal_op_fill_invite(op, update);
+ return sal_op_send_request(op,update);
}
return -1;
}
diff --git a/coreapi/bellesip_sal/sal_op_impl.c b/coreapi/bellesip_sal/sal_op_impl.c
index 945882efe..b3124300c 100644
--- a/coreapi/bellesip_sal/sal_op_impl.c
+++ b/coreapi/bellesip_sal/sal_op_impl.c
@@ -60,6 +60,7 @@ void sal_op_release_impl(SalOp *op){
if (op->pending_client_trans) belle_sip_object_unref(op->pending_client_trans);
if (op->pending_server_trans) belle_sip_object_unref(op->pending_server_trans);
+ if (op->pending_update_server_trans) belle_sip_object_unref(op->pending_update_server_trans);
if (op->event) belle_sip_object_unref(op->event);
__sal_op_free(op);
return ;
diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c
index bf83b831f..f39700f01 100644
--- a/coreapi/callbacks.c
+++ b/coreapi/callbacks.c
@@ -116,7 +116,7 @@ void linphone_core_update_streams(LinphoneCore *lc, LinphoneCall *call, SalMedia
video_stream_change_camera(call->videostream,lc->video_conf.device );
#endif
}
- /*FIXME ZRTP might be restarted in any cases ? */
+ /*FIXME ZRTP, might be restarted in any cases ? */
ms_message("No need to restart streams, SDP is unchanged.");
goto end;
}else {
@@ -407,7 +407,8 @@ static void call_accepted(SalOp *op){
linphone_call_fix_call_parameters(call);
if (!call->current_params.in_conference)
lc->current_call=call;
- linphone_call_set_state(call, LinphoneCallStreamsRunning, "Streams running");
+ if (call->prevstate != LinphoneCallIncomingEarlyMedia) /*don't change state in aswer to a SIP UPDATE in early media*/
+ linphone_call_set_state(call, LinphoneCallStreamsRunning, "Streams running");
}
}else{
/*send a bye*/
@@ -473,7 +474,7 @@ static void call_paused_by_remote(LinphoneCore *lc, LinphoneCall *call){
linphone_call_set_state (call,LinphoneCallPausedByRemote,"Call paused by remote");
}
-static void call_updated_by_remote(LinphoneCore *lc, LinphoneCall *call){
+static void call_updated_by_remote(LinphoneCore *lc, LinphoneCall *call,bool_t notify_application){
/*first check if media capabilities are compatible*/
SalMediaDescription* md;
linphone_call_make_local_media_description(lc,call);
@@ -484,16 +485,21 @@ static void call_updated_by_remote(LinphoneCore *lc, LinphoneCall *call){
return;
}
- if(lc->vtable.display_status)
- lc->vtable.display_status(lc,_("Call is updated by remote."));
- call->defer_update=FALSE;
- linphone_call_set_state(call, LinphoneCallUpdatedByRemote,"Call updated by remote");
- if (call->defer_update==FALSE){
- linphone_core_accept_call_update(lc,call,NULL);
+ if (notify_application) {
+ if(lc->vtable.display_status)
+ lc->vtable.display_status(lc,_("Call is updated by remote."));
+ call->defer_update=FALSE;
+ linphone_call_set_state(call, LinphoneCallUpdatedByRemote,"Call updated by remote");
+ if (call->defer_update==FALSE){
+ linphone_core_accept_call_update(lc,call,NULL);
+ }
+ } else { /*SIP UPDATE case*/
+ /*can be call from any state*/
+ _linphone_core_accept_call_update(lc,call,NULL);
}
}
-/* this callback is called when an incoming re-INVITE modifies the session*/
+/* this callback is called when an incoming re-INVITE/ SIP UPDATE modifies the session*/
static void call_updating(SalOp *op){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
@@ -512,12 +518,16 @@ static void call_updating(SalOp *op){
call_resumed(lc,call);
}else call_paused_by_remote(lc,call);
break;
+ /*SIP UPDATE CASE*/
+ case LinphoneCallOutgoingEarlyMedia:
+ call_updated_by_remote(lc,call,FALSE);
+ break;
case LinphoneCallStreamsRunning:
case LinphoneCallConnected:
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);
+ call_updated_by_remote(lc,call,TRUE);
}
break;
default:
diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c
index d56c41709..66f7efa43 100644
--- a/coreapi/linphonecore.c
+++ b/coreapi/linphonecore.c
@@ -3231,23 +3231,25 @@ int linphone_core_start_accept_call_update(LinphoneCore *lc, LinphoneCall *call)
linphone_core_update_streams (lc,call,md);
linphone_call_fix_call_parameters(call);
}
- linphone_call_set_state(call,LinphoneCallStreamsRunning,"Connected (streams running)");
+
+ if (call->state != LinphoneCallOutgoingEarlyMedia) /*don't change the state in case of outgoing early (SIP UPDATE)*/
+ linphone_call_set_state(call,LinphoneCallStreamsRunning,"Connected (streams running)");
return 0;
}
/**
* @ingroup call_control
* Accept call modifications initiated by other end.
- *
+ *
* This call may be performed in response to a #LinphoneCallUpdatedByRemote state notification.
* When such notification arrives, the application can decide to call linphone_core_defer_update_call() so that it can
* have the time to prompt the user. linphone_call_get_remote_params() can be used to get information about the call parameters
* requested by the other party, such as whether a video stream is requested.
- *
+ *
* When the user accepts or refuse the change, linphone_core_accept_call_update() can be done to answer to the other party.
* If params is NULL, then the same call parameters established before the update request will continue to be used (no change).
* If params is not NULL, then the update will be accepted according to the parameters passed.
- * Typical example is when a user accepts to start video, then params should indicate that video stream should be used
+ * Typical example is when a user accepts to start video, then params should indicate that video stream should be used
* (see linphone_call_params_enable_video()).
* @param lc the linphone core object.
* @param call the LinphoneCall object
@@ -3255,16 +3257,20 @@ int linphone_core_start_accept_call_update(LinphoneCore *lc, LinphoneCall *call)
* @return 0 if successful, -1 otherwise (actually when this function call is performed outside ot #LinphoneCallUpdatedByRemote state).
**/
int linphone_core_accept_call_update(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallParams *params){
- SalMediaDescription *remote_desc;
- bool_t keep_sdp_version;
-#ifdef VIDEO_ENABLED
- bool_t old_has_video = call->params.has_video;
-#endif
if (call->state!=LinphoneCallUpdatedByRemote){
ms_error("linphone_core_accept_update(): invalid state %s to call this function.",
linphone_call_state_to_string(call->state));
return -1;
}
+ return _linphone_core_accept_call_update(lc, call, params);
+}
+int _linphone_core_accept_call_update(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallParams *params){
+ SalMediaDescription *remote_desc;
+ bool_t keep_sdp_version;
+#ifdef VIDEO_ENABLED
+ bool_t old_has_video = call->params.has_video;
+#endif
+
remote_desc = sal_call_get_remote_media_description(call->op);
keep_sdp_version = lp_config_get_int(lc->config, "sip", "keep_sdp_version", 0);
if (keep_sdp_version &&(remote_desc->session_id == call->remote_session_id) && (remote_desc->session_ver == call->remote_session_ver)) {
diff --git a/coreapi/private.h b/coreapi/private.h
index f0ddaaeaa..b24a476fa 100644
--- a/coreapi/private.h
+++ b/coreapi/private.h
@@ -605,7 +605,7 @@ void linphone_tone_description_destroy(LinphoneToneDescription *obj);
LinphoneToneDescription *linphone_core_get_call_error_tone(const LinphoneCore *lc, LinphoneReason reason);
void linphone_core_play_call_error_tone(LinphoneCore *lc, LinphoneReason reason);
void _linphone_core_set_call_error_tone(LinphoneCore *lc, LinphoneReason reason, LinphoneToneID id, const char *audiofile);
-
+int _linphone_core_accept_call_update(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallParams *params);
typedef struct _LinphoneConference LinphoneConference;
struct _LinphoneCore
diff --git a/include/sal/sal.h b/include/sal/sal.h
index 44cc2dcef..bb4a37d30 100644
--- a/include/sal/sal.h
+++ b/include/sal/sal.h
@@ -420,7 +420,7 @@ typedef void (*SalOnCallReceived)(SalOp *op);
typedef void (*SalOnCallRinging)(SalOp *op);
typedef void (*SalOnCallAccepted)(SalOp *op);
typedef void (*SalOnCallAck)(SalOp *op);
-typedef void (*SalOnCallUpdating)(SalOp *op);/*< Called when a reINVITE is received*/
+typedef void (*SalOnCallUpdating)(SalOp *op);/*< Called when a reINVITE/UPDATE is received*/
typedef void (*SalOnCallTerminated)(SalOp *op, const char *from);
typedef void (*SalOnCallFailure)(SalOp *op, SalError error, SalReason reason, const char *details, int code);
typedef void (*SalOnCallReleased)(SalOp *salop);
diff --git a/tester/call_tester.c b/tester/call_tester.c
index aacd6a2c9..21188b9c1 100644
--- a/tester/call_tester.c
+++ b/tester/call_tester.c
@@ -1133,6 +1133,92 @@ static void early_media_call_with_ringing(void){
linphone_core_manager_destroy(pauline);
}
+static void early_media_call_with_update_base(bool_t media_change){
+ char hellopath[256];
+ LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc");
+ LinphoneCoreManager* pauline = linphone_core_manager_new("pauline_rc");
+ MSList* lcs = NULL;
+ LinphoneCall *marie_call, *pauline_call;
+ LinphoneCallParams *pauline_params;
+
+ lcs = ms_list_append(lcs,marie->lc);
+ lcs = ms_list_append(lcs,pauline->lc);
+ if (media_change) {
+ disable_all_codecs_except_one(marie->lc,"pcmu");
+ disable_all_codecs_except_one(pauline->lc,"pcmu");
+
+ }
+ /*
+ Marie calls Pauline, and after the call has rung, transitions to an early_media session
+ */
+
+ /*use playfile for callee to avoid locking on capture card*/
+ linphone_core_use_files (pauline->lc,TRUE);
+ snprintf(hellopath,sizeof(hellopath), "%s/sounds/hello8000.wav", liblinphone_tester_file_prefix);
+ linphone_core_set_play_file(pauline->lc,hellopath);
+
+ marie_call = linphone_core_invite_address(marie->lc, pauline->identity);
+
+ CU_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallIncomingReceived,1,1000));
+ CU_ASSERT_TRUE(wait_for_list(lcs, &marie->stat.number_of_LinphoneCallOutgoingRinging,1,1000));
+ /* send a 183 to initiate the early media */
+ linphone_core_accept_early_media(pauline->lc, linphone_core_get_current_call(pauline->lc));
+ CU_ASSERT_TRUE( wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallIncomingEarlyMedia,1,2000) );
+ CU_ASSERT_TRUE( wait_for_list(lcs, &marie->stat.number_of_LinphoneCallOutgoingEarlyMedia,1,2000) );
+
+ pauline_call = linphone_core_get_current_call(pauline->lc);
+ pauline_params = linphone_call_params_copy(linphone_call_get_current_params(pauline_call));
+
+ if (media_change) {
+ disable_all_codecs_except_one(marie->lc,"pcma");
+ disable_all_codecs_except_one(pauline->lc,"pcma");
+ }
+ #define UPDATED_SESSION_NAME "nouveau nom de session"
+
+ linphone_call_params_set_session_name(pauline_params,UPDATED_SESSION_NAME);
+ linphone_core_update_call(pauline->lc, pauline_call, pauline_params);
+
+ /*just to wait 2s*/
+ liblinphone_tester_check_rtcp(marie, pauline);
+ wait_for_list(lcs, &marie->stat.number_of_LinphoneCallUpdatedByRemote,100000,2000);
+
+ CU_ASSERT_STRING_EQUAL( linphone_call_params_get_session_name(linphone_call_get_remote_params(marie_call))
+ , UPDATED_SESSION_NAME);
+
+ linphone_core_accept_call(pauline->lc, linphone_core_get_current_call(pauline->lc));
+
+ CU_ASSERT_TRUE(wait_for_list(lcs, &marie->stat.number_of_LinphoneCallStreamsRunning, 1,1000));
+
+ CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneCallOutgoingEarlyMedia,1);
+ CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneCallStreamsRunning,1);
+ CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneCallConnected,1);
+
+ CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneCallIncomingEarlyMedia,1);
+ CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneCallStreamsRunning,1);
+ CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneCallConnected,1);
+ CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneCallUpdating,1);
+
+ liblinphone_tester_check_rtcp(marie, pauline);
+
+ linphone_core_terminate_all_calls(pauline->lc);
+
+ CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallEnd,1,1000));
+ CU_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallEnd,1,1000));
+
+
+ ms_list_free(lcs);
+
+ linphone_core_manager_destroy(marie);
+ linphone_core_manager_destroy(pauline);
+}
+
+static void early_media_call_with_session_update(void){
+ early_media_call_with_update_base(FALSE);
+}
+
+static void early_media_call_with_codec_update(void){
+ early_media_call_with_update_base(TRUE);
+}
static void simple_call_transfer(void) {
LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc");
@@ -1612,6 +1698,8 @@ test_t call_tests[] = {
{ "Simple call compatibility mode", simple_call_compatibility_mode },
{ "Early-media call", early_media_call },
{ "Early-media call with ringing", early_media_call_with_ringing },
+ { "Early-media call with updated media session", early_media_call_with_session_update},
+ { "Early-media call with updated codec", early_media_call_with_codec_update},
{ "Call terminated by caller", call_terminated_by_caller },
{ "Call without SDP", call_with_no_sdp},
{ "Call paused resumed", call_paused_resumed },