From 73539fefa145a32f30897cb1f30e4b222cbf5c3f Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 14 Sep 2016 14:27:06 +0200 Subject: [PATCH] Try to repair calls in early state when a network loss occurs. --- coreapi/bellesip_sal/sal_op_call.c | 14 +++-- coreapi/callbacks.c | 22 +++++++ coreapi/linphonecall.c | 24 +++++--- coreapi/private.h | 1 + include/sal/sal.h | 2 + tester/call_single_tester.c | 92 ++++++++++++++++++++++++++++++ 6 files changed, 144 insertions(+), 11 deletions(-) diff --git a/coreapi/bellesip_sal/sal_op_call.c b/coreapi/bellesip_sal/sal_op_call.c index c2f89cd6f..06dea0cea 100644 --- a/coreapi/bellesip_sal/sal_op_call.c +++ b/coreapi/bellesip_sal/sal_op_call.c @@ -295,7 +295,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); } @@ -496,6 +496,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 +879,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 +1087,11 @@ 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; +} diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index a82149d7f..d86e942a6 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -240,9 +240,24 @@ 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 *call = (LinphoneCall *)bctbx_list_get_data(it); + if (call->broken && sal_call_compare_op(h, call->op)) { + 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 +265,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); diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 70dd7f2ff..48d4cb77f 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -5027,7 +5027,7 @@ 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 LinphoneCallPaused: case LinphoneCallPausedByRemote: @@ -5056,13 +5056,15 @@ void linphone_call_repair_if_broken(LinphoneCall *call){ 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)) { + 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); } - params = linphone_core_create_call_params(call->core, call); - linphone_core_update_call(call->core, call, params); - linphone_call_params_unref(params); 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)); @@ -5080,3 +5082,11 @@ void linphone_call_refresh_sockets(LinphoneCall *call){ } } } + +void linphone_call_replace_op(LinphoneCall *call, SalOp *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); + sal_call_notify_ringing(call->op, (linphone_call_get_state(call) == LinphoneCallIncomingEarlyMedia) ? TRUE : FALSE); +} diff --git a/coreapi/private.h b/coreapi/private.h index 641bca192..fcce6908e 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -389,6 +389,7 @@ 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); 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 3f8de46fe..7f09e7b64 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -751,6 +751,8 @@ 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); /* Call test API */ diff --git a/tester/call_single_tester.c b/tester/call_single_tester.c index 691f1dfb1..8ff6e99b7 100644 --- a/tester/call_single_tester.c +++ b/tester/call_single_tester.c @@ -4189,6 +4189,95 @@ 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_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; + + 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: + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +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; + + 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_3(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 _call_with_network_switch(bool_t use_ice, bool_t with_socket_refresh, bool_t enable_rtt) { LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); @@ -5000,6 +5089,9 @@ test_t call_tests[] = { 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_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),