diff --git a/coreapi/bellesip_sal/sal_impl.h b/coreapi/bellesip_sal/sal_impl.h index e0fc4fc77..174d2b435 100644 --- a/coreapi/bellesip_sal/sal_impl.h +++ b/coreapi/bellesip_sal/sal_impl.h @@ -130,6 +130,7 @@ SalOp* sal_op_ref(SalOp* op); void* sal_op_unref(SalOp* op); void sal_op_release_impl(SalOp *op); +void sal_op_set_replaces(SalOp* op,belle_sip_header_replaces_t* replaces); void sal_op_set_remote_ua(SalOp*op,belle_sip_message_t* message); int sal_op_send_request(SalOp* op, belle_sip_request_t* request); int sal_op_send_request_with_expires(SalOp* op, belle_sip_request_t* request,int expires); diff --git a/coreapi/bellesip_sal/sal_op_call.c b/coreapi/bellesip_sal/sal_op_call.c index c2f89cd6f..8f7fa89ef 100644 --- a/coreapi/bellesip_sal/sal_op_call.c +++ b/coreapi/bellesip_sal/sal_op_call.c @@ -208,7 +208,7 @@ static void handle_sdp_from_response(SalOp* op,belle_sip_response_t* response) { if (op->base.local_media) sdp_process(op); } -static void cancelling_invite(SalOp* op ){ +void sal_call_cancel_invite(SalOp* op) { belle_sip_request_t* cancel; ms_message("Cancelling INVITE request from [%s] to [%s] ",sal_op_get_from(op), sal_op_get_to(op)); cancel = belle_sip_client_transaction_create_cancel(op->pending_client_trans); @@ -229,6 +229,10 @@ static void cancelling_invite(SalOp* op ){ break; } } +} + +static void cancelling_invite(SalOp *op) { + sal_call_cancel_invite(op); op->state=SalOpStateTerminating; } @@ -295,7 +299,7 @@ static void call_process_response(void *op_base, const belle_sip_response_event_ } } else if (code >=200 && code<300 - && strcmp("UPDATE",belle_sip_request_get_method(req))==0) { + && strcmp("UPDATE",method)==0) { handle_sdp_from_response(op,response); op->base.root->callbacks.call_accepted(op); } @@ -339,6 +343,9 @@ static void call_process_response(void *op_base, const belle_sip_response_event_ } }else if (strcmp("UPDATE",method)==0){ op->base.root->callbacks.call_accepted(op); /*INVITE*/ + }else if (strcmp("CANCEL",method)==0){ + sal_op_set_error_info_from_response(op,response); + op->base.root->callbacks.call_failure(op); } break; case SalOpStateTerminating: @@ -496,6 +503,7 @@ static int process_sdp_for_invite(SalOp* op,belle_sip_request_t* invite) { belle_sdp_session_description_t* sdp; int err=0; SalReason reason; + if (extract_sdp(op,BELLE_SIP_MESSAGE(invite),&sdp,&reason)==0) { if (sdp){ op->sdp_offering=FALSE; @@ -878,9 +886,9 @@ int sal_call_accept(SalOp*h){ return -1; } ms_message("Accepting server transaction [%p] on op [%p]", transaction, h); + /* sends a 200 OK */ response = sal_op_create_response_from_request(h,belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(transaction)),200); - if (response==NULL){ ms_error("Fail to build answer for call"); return -1; @@ -1086,6 +1094,24 @@ int sal_call_is_offerer(const SalOp *h){ return h->sdp_offering; } +bool_t sal_call_compare_op(const SalOp *op1, const SalOp *op2) { + if (strcmp(op1->base.call_id, op2->base.call_id) == 0) return TRUE; + return FALSE; +} +bool_t sal_call_dialog_request_pending(const SalOp *op) { + return belle_sip_dialog_request_pending(op->dialog) ? TRUE : FALSE; +} +const char * sal_call_get_local_tag(SalOp *op) { + return belle_sip_dialog_get_local_tag(op->dialog); +} +const char * sal_call_get_remote_tag(SalOp *op) { + return belle_sip_dialog_get_remote_tag(op->dialog); +} + +void sal_call_set_replaces(SalOp *op, const char *call_id, const char *from_tag, const char *to_tag) { + belle_sip_header_replaces_t *replaces = belle_sip_header_replaces_create(call_id, from_tag, to_tag); + sal_op_set_replaces(op, replaces); +} diff --git a/coreapi/bellesip_sal/sal_op_call_transfer.c b/coreapi/bellesip_sal/sal_op_call_transfer.c index a0aac3c20..8adb8564f 100644 --- a/coreapi/bellesip_sal/sal_op_call_transfer.c +++ b/coreapi/bellesip_sal/sal_op_call_transfer.c @@ -22,13 +22,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. /*call transfer*/ -static void sal_op_set_replaces(SalOp* op,belle_sip_header_replaces_t* replaces) { - if (op->replaces){ - belle_sip_object_unref(op->replaces); - } - op->replaces=replaces; - belle_sip_object_ref(op->replaces); -} static void sal_op_set_referred_by(SalOp* op,belle_sip_header_referred_by_t* referred_by) { if (op->referred_by){ belle_sip_object_unref(op->referred_by); diff --git a/coreapi/bellesip_sal/sal_op_impl.c b/coreapi/bellesip_sal/sal_op_impl.c index 23a8ea4ed..634bd22fb 100644 --- a/coreapi/bellesip_sal/sal_op_impl.c +++ b/coreapi/bellesip_sal/sal_op_impl.c @@ -30,6 +30,11 @@ SalOp * sal_op_new(Sal *sal){ return op; } +void sal_op_kill_dialog(SalOp *op) { + ms_warning("op [%p]: force kill of dialog [%p]", op, op->dialog); + belle_sip_dialog_delete(op->dialog); +} + void sal_op_release(SalOp *op){ /*if in terminating state, keep this state because it means we are waiting for a response to be able to terminate the operation.*/ if (op->state!=SalOpStateTerminating) @@ -235,6 +240,14 @@ int sal_ping(SalOp *op, const char *from, const char *to){ return sal_op_send_request(op,sal_op_build_request(op,"OPTIONS")); } +void sal_op_set_replaces(SalOp* op,belle_sip_header_replaces_t* replaces) { + if (op->replaces){ + belle_sip_object_unref(op->replaces); + } + op->replaces=replaces; + belle_sip_object_ref(op->replaces); +} + void sal_op_set_remote_ua(SalOp*op,belle_sip_message_t* message) { belle_sip_header_user_agent_t* user_agent=belle_sip_message_get_header_by_type(message,belle_sip_header_user_agent_t); char user_agent_string[256]; diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index a82149d7f..987e8fc57 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -240,9 +240,28 @@ static bool_t already_a_call_with_remote_address(const LinphoneCore *lc, const L } +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; @@ -250,6 +269,13 @@ static void call_received(SalOp *h){ SalMediaDescription *md; const char * p_asserted_id; + /* 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; + } + /* 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); @@ -421,8 +447,7 @@ static void call_ringing(SalOp *h){ /*already doing early media */ return; } - if (lc->ringstream!=NULL) return;/*already ringing !*/ - start_remote_ring(lc, call); + 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"); @@ -659,6 +684,7 @@ static void call_updated(LinphoneCore *lc, LinphoneCall *call, SalOp *op, bool_t 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{ @@ -672,7 +698,6 @@ static void call_updated(LinphoneCore *lc, LinphoneCall *call, SalOp *op, bool_t case LinphoneCallUpdating: case LinphoneCallPausing: case LinphoneCallResuming: - case LinphoneCallUpdatedByRemote: sal_call_decline(call->op,SalReasonInternalError,NULL); /*no break*/ case LinphoneCallIdle: @@ -918,6 +943,10 @@ static void call_failure(SalOp *op){ msg=_("Incompatible media parameters."); linphone_core_notify_display_status(lc,msg); break; + case SalReasonNoMatch: + /* Call leg does not exist response for case of section 5.5 of RFC 6141 */ + linphone_call_reinvite_to_recover_from_connection_loss(call); + return; /* Do not continue... */ default: linphone_core_notify_display_status(lc,_("Call failed.")); } diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index cfcc42554..0da520f02 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -5039,10 +5039,14 @@ void linphone_call_set_broken(LinphoneCall *call){ case LinphoneCallIncomingEarlyMedia: /*during the early states, the SAL layer reports the failure from the dialog or transaction layer, * hence, there is nothing special to do*/ - break; + //break; case LinphoneCallStreamsRunning: + case LinphoneCallUpdating: + case LinphoneCallPausing: + case LinphoneCallResuming: case LinphoneCallPaused: case LinphoneCallPausedByRemote: + case LinphoneCallUpdatedByRemote: /*during these states, the dialog is established. A failure of a transaction is not expected to close it. * Instead we have to repair the dialog by sending a reINVITE*/ call->broken = TRUE; @@ -5054,9 +5058,28 @@ void linphone_call_set_broken(LinphoneCall *call){ } } -void linphone_call_repair_if_broken(LinphoneCall *call){ - LinphoneCallParams *params; +static void linphone_call_repair_by_invite_with_replaces(LinphoneCall *call) { + const char *call_id = sal_op_get_call_id(call->op); + const char *from_tag = sal_call_get_local_tag(call->op); + const char *to_tag = sal_call_get_remote_tag(call->op); + sal_op_kill_dialog(call->op); + linphone_call_create_op(call); + sal_call_set_replaces(call->op, call_id, from_tag, to_tag); + linphone_core_start_invite(call->core, call, NULL); +} +void linphone_call_reinvite_to_recover_from_connection_loss(LinphoneCall *call) { + LinphoneCallParams *params; + ms_message("LinphoneCall[%p] is going to be updated (reINVITE) in order to recover from lost connectivity", call); + if (call->ice_session){ + ice_session_restart(call->ice_session, IR_Controlling); + } + params = linphone_core_create_call_params(call->core, call); + linphone_core_update_call(call->core, call, params); + linphone_call_params_unref(params); +} + +void linphone_call_repair_if_broken(LinphoneCall *call){ if (!call->broken) return; if (!call->core->media_network_reachable) return; @@ -5070,19 +5093,34 @@ void linphone_call_repair_if_broken(LinphoneCall *call){ switch (call->state){ + case LinphoneCallUpdating: + case LinphoneCallPausing: + if (sal_call_dialog_request_pending(call->op)) { + /* Need to cancel first re-INVITE as described in section 5.5 of RFC 6141 */ + sal_call_cancel_invite(call->op); + } + break; case LinphoneCallStreamsRunning: case LinphoneCallPaused: case LinphoneCallPausedByRemote: - ms_message("LinphoneCall[%p] is going to be updated (reINVITE) in order to recover from lost connectivity", call); - if (call->ice_session){ - ice_session_restart(call->ice_session, IR_Controlling); + if (!sal_call_dialog_request_pending(call->op)) { + linphone_call_reinvite_to_recover_from_connection_loss(call); } - params = linphone_core_create_call_params(call->core, call); - linphone_core_update_call(call->core, call, params); - linphone_call_params_unref(params); - break; + break; + case LinphoneCallUpdatedByRemote: + if (sal_call_dialog_request_pending(call->op)) { + sal_call_decline(call->op, SalReasonServiceUnavailable, NULL); + } + linphone_call_reinvite_to_recover_from_connection_loss(call); + break; + case LinphoneCallOutgoingEarlyMedia: + case LinphoneCallOutgoingInit: + case LinphoneCallOutgoingProgress: + case LinphoneCallOutgoingRinging: + linphone_call_repair_by_invite_with_replaces(call); + break; default: - ms_warning("linphone_call_resume_if_broken(): don't know what to do in state [%s]", linphone_call_state_to_string(call->state)); + ms_warning("linphone_call_repair_if_broken(): don't know what to do in state [%s]", linphone_call_state_to_string(call->state)); call->broken = FALSE; break; } @@ -5097,3 +5135,32 @@ void linphone_call_refresh_sockets(LinphoneCall *call){ } } } + +void linphone_call_replace_op(LinphoneCall *call, SalOp *op) { + switch (linphone_call_get_state(call)) { + case LinphoneCallConnected: + case LinphoneCallStreamsRunning: + sal_call_terminate(call->op); + break; + default: + break; + } + sal_op_kill_dialog(call->op); + sal_op_release(call->op); + call->op = op; + sal_op_set_user_pointer(call->op, call); + sal_call_set_local_media_description(call->op, call->localdesc); + switch (linphone_call_get_state(call)) { + case LinphoneCallIncomingEarlyMedia: + case LinphoneCallIncomingReceived: + sal_call_notify_ringing(call->op, (linphone_call_get_state(call) == LinphoneCallIncomingEarlyMedia) ? TRUE : FALSE); + break; + case LinphoneCallConnected: + case LinphoneCallStreamsRunning: + sal_call_accept(call->op); + break; + default: + ms_warning("linphone_call_replace_op(): don't know what to do in state [%s]", linphone_call_state_to_string(call->state)); + break; + } +} diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 3966a7ffe..8de0807a8 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -3622,8 +3622,14 @@ int linphone_core_update_call(LinphoneCore *lc, LinphoneCall *call, const Linpho case LinphoneCallStreamsRunning: case LinphoneCallPaused: case LinphoneCallPausedByRemote: + case LinphoneCallUpdatedByRemote: nextstate=LinphoneCallUpdating; break; + case LinphoneCallPausing: + case LinphoneCallResuming: + case LinphoneCallUpdating: + nextstate=call->state; + break; default: ms_error("linphone_core_update_call() is not allowed in [%s] state",linphone_call_state_to_string(call->state)); return -1; diff --git a/coreapi/private.h b/coreapi/private.h index 99c72c6c1..69e9e2334 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -390,6 +390,8 @@ void linphone_call_log_destroy(LinphoneCallLog *cl); void linphone_call_set_transfer_state(LinphoneCall* call, LinphoneCallState state); LinphonePlayer *linphone_call_build_player(LinphoneCall*call); void linphone_call_refresh_sockets(LinphoneCall *call); +void linphone_call_replace_op(LinphoneCall *call, SalOp *op); +void linphone_call_reinvite_to_recover_from_connection_loss(LinphoneCall *call); LinphoneCallParams * linphone_call_params_new(void); SalMediaProto get_proto_from_call_params(const LinphoneCallParams *params); diff --git a/include/sal/sal.h b/include/sal/sal.h index af3fcf1f7..57ae2b7ec 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -680,6 +680,7 @@ SalOp *sal_op_ref(SalOp* h); void sal_op_stop_refreshing(SalOp *op); int sal_op_refresh(SalOp *op); +void sal_op_kill_dialog(SalOp *op); void sal_op_release(SalOp *h); /*same as release, but does not stop refresher if any*/ void* sal_op_unref(SalOp* op); @@ -737,6 +738,7 @@ int sal_call_notify_ringing(SalOp *h, bool_t early_media); int sal_call_accept(SalOp*h); int sal_call_decline(SalOp *h, SalReason reason, const char *redirection /*optional*/); int sal_call_update(SalOp *h, const char *subject, bool_t no_user_consent); +void sal_call_cancel_invite(SalOp *op); SalMediaDescription * sal_call_get_remote_media_description(SalOp *h); LINPHONE_PUBLIC SalMediaDescription * sal_call_get_final_media_description(SalOp *h); int sal_call_refer(SalOp *h, const char *refer_to); @@ -752,6 +754,11 @@ bool_t sal_call_autoanswer_asked(SalOp *op); void sal_call_send_vfu_request(SalOp *h); int sal_call_is_offerer(const SalOp *h); int sal_call_notify_refer_state(SalOp *h, SalOp *newcall); +bool_t sal_call_compare_op(const SalOp *op1, const SalOp *op2); +bool_t sal_call_dialog_request_pending(const SalOp *op); +const char * sal_call_get_local_tag(SalOp *op); +const char * sal_call_get_remote_tag(SalOp *op); +void sal_call_set_replaces(SalOp *op, const char *call_id, const char *from_tag, const char *to_tag); /* Call test API */ diff --git a/tester/call_single_tester.c b/tester/call_single_tester.c index 8b11a543d..a58d19745 100644 --- a/tester/call_single_tester.c +++ b/tester/call_single_tester.c @@ -1550,17 +1550,11 @@ void call_paused_resumed_base(bool_t multicast, bool_t with_losses) { BC_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallPausing,2)); BC_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallPausedByRemote,2)); BC_ASSERT_TRUE(wait_for_until(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallPaused,2,1000)); - /*now try to resume*/ + /*now try to resume, it should be OK*/ sal_set_send_error(pauline->lc->sal,0); linphone_core_resume_call(pauline->lc,call_pauline); - BC_ASSERT_FALSE(wait_for_until(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallStreamsRunning,3,2000)); - BC_ASSERT_FALSE(wait_for_until(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallStreamsRunning,3,2000)); - /*resume failed because ACK not received to re-invite is rejected*/ - /*next try is ok*/ - linphone_core_resume_call(pauline->lc,call_pauline); - BC_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallStreamsRunning,3)); - BC_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallStreamsRunning,3)); - + BC_ASSERT_TRUE(wait_for_until(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallStreamsRunning,3,2000)); + BC_ASSERT_TRUE(wait_for_until(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallStreamsRunning,3,2000)); } @@ -4156,26 +4150,28 @@ static void call_record_with_custom_rtp_modifier(void) { custom_rtp_modifier(FALSE, TRUE); } -static void _call_with_network_switch_in_early_state(bool_t network_loosed_by_caller){ +static void recovered_call_on_network_switch_in_early_state_1(void) { + LinphoneCall *incoming_call; LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); linphone_core_invite_address(marie->lc, pauline->identity); if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallIncomingReceived, 1))) goto end; if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallOutgoingRinging, 1))) goto end; - if (network_loosed_by_caller){ - linphone_core_set_network_reachable(marie->lc, FALSE); - BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallError, 1)); - linphone_core_set_network_reachable(marie->lc, TRUE); - linphone_core_terminate_call(pauline->lc, linphone_core_get_current_call(pauline->lc)); - BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallEnd, 1)); - }else{ - linphone_core_set_network_reachable(pauline->lc, FALSE); - BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallError, 1)); - linphone_core_set_network_reachable(pauline->lc, TRUE); - linphone_core_terminate_call(marie->lc, linphone_core_get_current_call(marie->lc)); - BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallEnd, 1)); - } + + linphone_core_set_network_reachable(marie->lc, FALSE); + wait_for(marie->lc, pauline->lc, &marie->stat.number_of_NetworkReachableFalse, 1); + linphone_core_set_network_reachable(marie->lc, TRUE); + wait_for(marie->lc, pauline->lc, &marie->stat.number_of_NetworkReachableTrue, 2); + + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallOutgoingRinging, 2)); + incoming_call = linphone_core_get_current_call(pauline->lc); + linphone_core_accept_call(pauline->lc, incoming_call); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, 1)); + + linphone_core_terminate_call(pauline->lc, incoming_call); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallEnd, 1)); BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallReleased, 1)); BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallReleased, 1)); end: @@ -4183,12 +4179,285 @@ end: linphone_core_manager_destroy(pauline); } -static void call_with_network_switch_in_early_state_1(void){ - _call_with_network_switch_in_early_state(TRUE); +static void recovered_call_on_network_switch_in_early_state_2(void) { + LinphoneCall *incoming_call; + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); + + linphone_core_invite_address(marie->lc, pauline->identity); + if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallIncomingReceived, 1))) goto end; + if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallOutgoingRinging, 1))) goto end; + + incoming_call = linphone_core_get_current_call(pauline->lc); + linphone_core_accept_call(pauline->lc, incoming_call); + //linphone_core_iterate(pauline->lc); + linphone_core_set_network_reachable(marie->lc, FALSE); + wait_for(marie->lc, pauline->lc, &marie->stat.number_of_NetworkReachableFalse, 1); + linphone_core_set_network_reachable(marie->lc, TRUE); + wait_for(marie->lc, pauline->lc, &marie->stat.number_of_NetworkReachableTrue, 2); + + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, 1)); + + linphone_core_terminate_call(pauline->lc, incoming_call); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallEnd, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallReleased, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallReleased, 1)); +end: + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); } -static void call_with_network_switch_in_early_state_2(void){ - _call_with_network_switch_in_early_state(FALSE); +static void recovered_call_on_network_switch_in_early_state_3(void) { + LinphoneCall *incoming_call; + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); + + linphone_core_invite_address(marie->lc, pauline->identity); + if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallIncomingReceived, 1))) goto end; + if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallOutgoingRinging, 1))) goto end; + + linphone_core_set_network_reachable(pauline->lc, FALSE); + wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_NetworkReachableFalse, 1); + linphone_core_set_network_reachable(pauline->lc, TRUE); + wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_NetworkReachableTrue, 2); + + wait_for_until(marie->lc, pauline->lc, NULL, 1, 2000); + incoming_call = linphone_core_get_current_call(pauline->lc); + linphone_core_accept_call(pauline->lc, incoming_call); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, 1)); + + linphone_core_terminate_call(pauline->lc, incoming_call); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallEnd, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallReleased, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallReleased, 1)); +end: + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +static void recovered_call_on_network_switch_in_early_state_4(void) { + LinphoneCall *incoming_call; + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new("pauline_tcp_rc"); + + linphone_core_invite_address(marie->lc, pauline->identity); + if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallIncomingReceived, 1))) goto end; + if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallOutgoingRinging, 1))) goto end; + + incoming_call = linphone_core_get_current_call(pauline->lc); + linphone_core_accept_call(pauline->lc, incoming_call); + linphone_core_set_network_reachable(pauline->lc, FALSE); + wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_NetworkReachableFalse, 1); + linphone_core_set_network_reachable(pauline->lc, TRUE); + wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_NetworkReachableTrue, 2); + + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, 1)); + + BC_ASSERT_TRUE(sal_call_dialog_request_pending(incoming_call->op)); + wait_for_until(marie->lc, pauline->lc, NULL, 1, 2000); + BC_ASSERT_FALSE(sal_call_dialog_request_pending(incoming_call->op)); + linphone_core_terminate_call(pauline->lc, incoming_call); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallEnd, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallReleased, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallReleased, 1)); +end: + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +static void recovered_call_on_network_switch_during_reinvite_1(void) { + LinphoneCall *incoming_call; + LinphoneCall *outgoing_call; + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new("pauline_tcp_rc"); + + linphone_core_invite_address(marie->lc, pauline->identity); + if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallIncomingReceived, 1))) goto end; + if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallOutgoingRinging, 1))) goto end; + + incoming_call = linphone_core_get_current_call(pauline->lc); + linphone_core_accept_call(pauline->lc, incoming_call); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, 1)); + + outgoing_call = linphone_core_get_current_call(marie->lc); + linphone_core_pause_call(marie->lc, outgoing_call); + linphone_core_set_network_reachable(marie->lc, FALSE); + wait_for(marie->lc, pauline->lc, &marie->stat.number_of_NetworkReachableFalse, 1); + linphone_core_set_network_reachable(marie->lc, TRUE); + wait_for(marie->lc, pauline->lc, &marie->stat.number_of_NetworkReachableTrue, 2); + + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallPaused, 1)); + linphone_core_terminate_call(pauline->lc, incoming_call); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallEnd, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallReleased, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallReleased, 1)); +end: + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +static void configure_video_policies_for_network_switch(LinphoneCore *marie, LinphoneCore *pauline) { + LinphoneVideoPolicy policy; + policy.automatically_accept = FALSE; + policy.automatically_initiate = FALSE; + + linphone_core_enable_video_capture(marie, TRUE); + linphone_core_enable_video_display(marie, TRUE); + linphone_core_enable_video_capture(pauline, TRUE); + linphone_core_enable_video_display(pauline, TRUE); + linphone_core_set_video_policy(marie, &policy); + linphone_core_set_video_policy(pauline, &policy); + lp_config_set_int(pauline->config, "sip", "defer_update_default", TRUE); +} + +static void recovered_call_on_network_switch_during_reinvite_2(void) { + LinphoneCall *incoming_call; + LinphoneCall *outgoing_call; + LinphoneCallParams *params; + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new("pauline_tcp_rc"); + + configure_video_policies_for_network_switch(marie->lc, pauline->lc); + linphone_core_invite_address(marie->lc, pauline->identity); + if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallIncomingReceived, 1))) goto end; + if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallOutgoingRinging, 1))) goto end; + + incoming_call = linphone_core_get_current_call(pauline->lc); + linphone_core_accept_call(pauline->lc, incoming_call); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, 1)); + + outgoing_call = linphone_core_get_current_call(marie->lc); + params = linphone_core_create_call_params(marie->lc, outgoing_call); + linphone_call_params_enable_video(params, TRUE); + linphone_core_update_call(marie->lc, outgoing_call, params); + linphone_call_params_unref(params); + linphone_core_set_network_reachable(marie->lc, FALSE); + wait_for(marie->lc, pauline->lc, &marie->stat.number_of_NetworkReachableFalse, 1); + linphone_core_set_network_reachable(marie->lc, TRUE); + wait_for(marie->lc, pauline->lc, &marie->stat.number_of_NetworkReachableTrue, 2); + + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallUpdatedByRemote, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneRegistrationOk, 2)); + wait_for_until(marie->lc, pauline->lc, NULL, 1, 2000); + params = linphone_core_create_call_params(pauline->lc, incoming_call); + linphone_call_params_enable_video(params, TRUE); + linphone_core_accept_call_update(pauline->lc, incoming_call, params); + linphone_call_params_unref(params); + + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, 2)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, 2)); + wait_for_until(marie->lc, pauline->lc, NULL, 1, 2000); + linphone_core_terminate_call(pauline->lc, incoming_call); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallEnd, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallReleased, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallReleased, 1)); +end: + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +static void recovered_call_on_network_switch_during_reinvite_3(void) { + LinphoneCall *incoming_call; + LinphoneCall *outgoing_call; + LinphoneCallParams *params; + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new("pauline_tcp_rc"); + + configure_video_policies_for_network_switch(marie->lc, pauline->lc); + linphone_core_invite_address(marie->lc, pauline->identity); + if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallIncomingReceived, 1))) goto end; + if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallOutgoingRinging, 1))) goto end; + + incoming_call = linphone_core_get_current_call(pauline->lc); + linphone_core_accept_call(pauline->lc, incoming_call); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, 1)); + + outgoing_call = linphone_core_get_current_call(marie->lc); + params = linphone_core_create_call_params(marie->lc, outgoing_call); + linphone_call_params_enable_video(params, TRUE); + linphone_core_update_call(marie->lc, outgoing_call, params); + linphone_call_params_unref(params); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallUpdatedByRemote, 1)); + + linphone_core_set_network_reachable(pauline->lc, FALSE); + wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_NetworkReachableFalse, 1); + linphone_core_set_network_reachable(pauline->lc, TRUE); + wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_NetworkReachableTrue, 2); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneRegistrationOk, 2)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallUpdatedByRemote, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, 2)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, 2)); + + params = linphone_core_create_call_params(marie->lc, outgoing_call); + linphone_call_params_enable_video(params, TRUE); + linphone_core_update_call(marie->lc, outgoing_call, params); + linphone_call_params_unref(params); + wait_for_until(marie->lc, pauline->lc, NULL, 1, 2000); + params = linphone_core_create_call_params(pauline->lc, incoming_call); + linphone_call_params_enable_video(params, TRUE); + linphone_core_accept_call_update(pauline->lc, incoming_call, params); + linphone_call_params_unref(params); + + wait_for_until(marie->lc, pauline->lc, NULL, 1, 2000); + linphone_core_terminate_call(pauline->lc, incoming_call); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallEnd, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallReleased, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallReleased, 1)); +end: + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +static void recovered_call_on_network_switch_during_reinvite_4(void) { + LinphoneCall *incoming_call; + LinphoneCall *outgoing_call; + LinphoneCallParams *params; + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new("pauline_tcp_rc"); + + configure_video_policies_for_network_switch(marie->lc, pauline->lc); + linphone_core_invite_address(marie->lc, pauline->identity); + if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallIncomingReceived, 1))) goto end; + if (!BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallOutgoingRinging, 1))) goto end; + + incoming_call = linphone_core_get_current_call(pauline->lc); + linphone_core_accept_call(pauline->lc, incoming_call); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, 1)); + + outgoing_call = linphone_core_get_current_call(marie->lc); + params = linphone_core_create_call_params(marie->lc, outgoing_call); + linphone_call_params_enable_video(params, TRUE); + linphone_core_update_call(marie->lc, outgoing_call, params); + linphone_call_params_unref(params); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallUpdatedByRemote, 1)); + + params = linphone_core_create_call_params(pauline->lc, incoming_call); + linphone_call_params_enable_video(params, TRUE); + linphone_core_accept_call_update(pauline->lc, incoming_call, params); + linphone_call_params_unref(params); + linphone_core_set_network_reachable(pauline->lc, FALSE); + wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_NetworkReachableFalse, 1); + linphone_core_set_network_reachable(pauline->lc, TRUE); + wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_NetworkReachableTrue, 2); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneRegistrationOk, 2)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, 2)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, 2)); + + wait_for_until(marie->lc, pauline->lc, NULL, 1, 2000); + linphone_core_terminate_call(pauline->lc, incoming_call); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallEnd, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallReleased, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallReleased, 1)); +end: + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); } static void _call_with_network_switch(bool_t use_ice, bool_t with_socket_refresh, bool_t enable_rtt) { @@ -5073,8 +5342,14 @@ test_t call_tests[] = { TEST_NO_TAG("Call paused resumed with custom RTP Modifier", call_paused_resumed_with_custom_rtp_modifier), TEST_NO_TAG("Call record with custom RTP Modifier", call_record_with_custom_rtp_modifier), TEST_NO_TAG("Call with network switch", call_with_network_switch), - TEST_NO_TAG("Call with network switch in early state 1", call_with_network_switch_in_early_state_1), - TEST_NO_TAG("Call with network switch in early state 2", call_with_network_switch_in_early_state_2), + TEST_NO_TAG("Recovered call on network switch in early state 1", recovered_call_on_network_switch_in_early_state_1), + TEST_NO_TAG("Recovered call on network switch in early state 2", recovered_call_on_network_switch_in_early_state_2), + TEST_NO_TAG("Recovered call on network switch in early state 3", recovered_call_on_network_switch_in_early_state_3), + TEST_NO_TAG("Recovered call on network switch in early state 4", recovered_call_on_network_switch_in_early_state_4), + TEST_NO_TAG("Recovered call on network switch during re-invite 1", recovered_call_on_network_switch_during_reinvite_1), + TEST_NO_TAG("Recovered call on network switch during re-invite 2", recovered_call_on_network_switch_during_reinvite_2), + TEST_NO_TAG("Recovered call on network switch during re-invite 3", recovered_call_on_network_switch_during_reinvite_3), + TEST_NO_TAG("Recovered call on network switch during re-invite 4", recovered_call_on_network_switch_during_reinvite_4), TEST_ONE_TAG("Call with network switch and ICE", call_with_network_switch_and_ice, "ICE"), TEST_ONE_TAG("Call with network switch, ICE and RTT", call_with_network_switch_ice_and_rtt, "ICE"), TEST_NO_TAG("Call with network switch with socket refresh", call_with_network_switch_and_socket_refresh),