diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index b5e8d62b9..ed2975358 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -3651,16 +3651,23 @@ static void report_bandwidth(LinphoneCall *call, MediaStream *as, MediaStream *v } -static void linphone_core_disconnected(LinphoneCore *lc, LinphoneCall *call){ - char temp[256]={0}; +static void linphone_call_lost(LinphoneCall *call, LinphoneReason reason){ + LinphoneCore *lc = call->core; + char *temp = NULL; char *from=NULL; from = linphone_call_get_remote_address_as_string(call); - snprintf(temp,sizeof(temp)-1,"Remote end %s seems to have disconnected, the call is going to be closed.",from ? from : ""); + switch(reason){ + case LinphoneReasonIOError: + temp = ms_strdup_printf("Call with %s disconnected because of network, it is going to be closed.", from ? from : "?"); + break; + default: + temp = ms_strdup_printf("Media connectivity with %s is lost, call is going to be closed.", from ? from : "?"); + break; + } if (from) ms_free(from); - - ms_message("On call [%p]: %s",call,temp); - linphone_core_notify_display_warning(lc,temp); + ms_message("LinphoneCall [%p]: %s",call, temp); + linphone_core_notify_display_warning(lc, temp); linphone_core_terminate_call(lc,call); linphone_core_play_named_tone(lc,LinphoneToneCallLost); } @@ -3928,7 +3935,7 @@ void linphone_call_background_tasks(LinphoneCall *call, bool_t one_second_elapse && call->audiostream->ms.state==MSStreamStarted && disconnect_timeout>0 ) disconnected=!audio_stream_alive(call->audiostream,disconnect_timeout); if (disconnected) - linphone_core_disconnected(call->core,call); + linphone_call_lost(call, LinphoneReasonUnknown); } void linphone_call_log_completed(LinphoneCall *call){ @@ -4234,3 +4241,52 @@ RtpTransport* linphone_call_get_meta_rtcp_transport(LinphoneCall *call, int stre rtp_session_get_transports(call->sessions[stream_index].rtp_session, &meta_rtp, &meta_rtcp); return meta_rtcp; } + +void linphone_call_set_broken(LinphoneCall *call){ + switch(call->state){ + /*for all the early states, we prefer to drop the call*/ + case LinphoneCallOutgoingInit: + case LinphoneCallOutgoingRinging: + case LinphoneCallOutgoingEarlyMedia: + case LinphoneCallIncomingReceived: + case LinphoneCallIncomingEarlyMedia: + linphone_call_lost(call, LinphoneReasonIOError); + break; + case LinphoneCallStreamsRunning: + case LinphoneCallPaused: + case LinphoneCallPausedByRemote: + call->broken = TRUE; + break; + default: + ms_error("linphone_call_set_broken() unimplemented case."); + break; + } +} + +void linphone_call_repair_if_broken(LinphoneCall *call){ + LinphoneCallParams *params; + + if (!call->broken) return; + + /*First, make sure that the proxy from which we received this call, or to which we routed this call is registered*/ + if (!call->dest_proxy || linphone_proxy_config_get_state(call->dest_proxy) != LinphoneRegistrationOk) return; + + + switch (call->state){ + 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); + ice_session_set_role(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); + break; + default: + ms_error("linphone_call_resume_if_broken(): 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 1c238a4aa..fc18ce331 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -3281,6 +3281,7 @@ int linphone_core_update_call(LinphoneCore *lc, LinphoneCall *call, const Linpho } if (params!=NULL){ + call->broken = FALSE; linphone_call_set_state(call,nextstate,"Updating call"); #if defined(VIDEO_ENABLED) && defined(BUILD_UPNP) has_video = call->params->has_video; @@ -6200,6 +6201,8 @@ static void set_network_reachable(LinphoneCore* lc,bool_t isReachable, time_t cu if (!lc->network_reachable){ linphone_core_invalidate_friend_subscriptions(lc); sal_reset_transports(lc->sal); + /*mark all calls as broken, so that they can be either dropped immediately or restaured when network will be back*/ + ms_list_for_each(lc->calls, (MSIterateFunc) linphone_call_set_broken); }else{ linphone_core_resolve_stun_server(lc); } diff --git a/coreapi/misc.c b/coreapi/misc.c index 60465887d..7547f1022 100644 --- a/coreapi/misc.c +++ b/coreapi/misc.c @@ -1833,4 +1833,3 @@ void linphone_task_list_run(LinphoneTaskList *t){ void linphone_task_list_free(LinphoneTaskList *t){ t->hooks = ms_list_free_with_data(t->hooks, (void (*)(void*))ms_free); } - diff --git a/coreapi/private.h b/coreapi/private.h index 6d5bc08d3..afcae52a8 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -311,6 +311,7 @@ struct _LinphoneCall{ bool_t record_active; bool_t paused_by_app; + bool_t broken; /*set to TRUE when the call is in broken state due to network disconnection or transport */ }; BELLE_SIP_DECLARE_VPTR(LinphoneCall); @@ -935,6 +936,8 @@ LinphoneEcCalibratorStatus ec_calibrator_get_status(EcCalibrator *ecc); void ec_calibrator_destroy(EcCalibrator *ecc); void linphone_call_background_tasks(LinphoneCall *call, bool_t one_second_elapsed); +void linphone_call_set_broken(LinphoneCall *call); +void linphone_call_repair_if_broken(LinphoneCall *call); void linphone_core_preempt_sound_resources(LinphoneCore *lc); int _linphone_core_pause_call(LinphoneCore *lc, LinphoneCall *call); diff --git a/coreapi/proxy.c b/coreapi/proxy.c index 5378236a1..b22b5f29a 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -1512,8 +1512,13 @@ void linphone_proxy_config_set_state(LinphoneProxyConfig *cfg, LinphoneRegistrat if (update_friends){ linphone_core_update_friends_subscriptions(lc,cfg,TRUE); } - if (lc) + if (lc){ linphone_core_notify_registration_state_changed(lc,cfg,state,message); + if (lc->calls && lp_config_get_int(lc->config, "sip", "repair_broken_calls", 1)){ + /*if we are registered and there were broken calls due to a past network disconnection, attempt to repair them*/ + ms_list_for_each(lc->calls, (MSIterateFunc) linphone_call_repair_if_broken); + } + } } else { /*state already reported*/ } diff --git a/tester/call_tester.c b/tester/call_tester.c index 953632bb7..dc54182cb 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -4901,6 +4901,55 @@ static void call_record_with_custom_rtp_modifier(void) { custom_rtp_modifier(FALSE, TRUE); } +static void _call_with_network_switch(bool_t use_ice){ + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); + bool_t call_ok; + + if (use_ice){ + linphone_core_set_firewall_policy(marie->lc,LinphonePolicyUseIce); + linphone_core_set_firewall_policy(pauline->lc,LinphonePolicyUseIce); + } + + BC_ASSERT_TRUE((call_ok=call(pauline,marie))); + if (!call_ok) goto end; + + wait_for_until(marie->lc, pauline->lc, NULL, 0, 2000); + if (use_ice) BC_ASSERT_TRUE(check_ice(pauline,marie,LinphoneIceStateHostConnection)); + + /*marie looses the network and reconnects*/ + linphone_core_set_network_reachable(marie->lc, FALSE); + wait_for_until(marie->lc, pauline->lc, NULL, 0, 1000); + + /*marie will reconnect and register*/ + linphone_core_set_network_reachable(marie->lc, TRUE); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneRegistrationOk, 2)); + + /*pauline shall receive a reINVITE to update the session*/ + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallUpdatedByRemote, 1)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning, 2)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning, 2)); + + liblinphone_tester_check_rtcp(pauline, marie); + if (use_ice) BC_ASSERT_TRUE(check_ice(pauline,marie,LinphoneIceStateHostConnection)); + + /*pauline shall be able to end the call without problem now*/ + end_call(pauline, marie); +end: + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +static void call_with_network_switch(void){ + _call_with_network_switch(FALSE); +} + +#if 0 +static void call_with_network_switch_and_ice(void){ + _call_with_network_switch(TRUE); +} +#endif + test_t call_tests[] = { { "Early declined call", early_declined_call }, { "Call declined", call_declined }, @@ -5033,7 +5082,12 @@ test_t call_tests[] = { { "Call with complex late offering", call_with_complex_late_offering }, { "Call with custom RTP Modifier", call_with_custom_rtp_modifier }, { "Call paused resumed with custom RTP Modifier", call_paused_resumed_with_custom_rtp_modifier }, - { "Call record with custom RTP Modifier", call_record_with_custom_rtp_modifier } + { "Call record with custom RTP Modifier", call_record_with_custom_rtp_modifier }, + { "Call with network switch", call_with_network_switch } +#if 0 + , + { "Call with network switch and ICE", call_with_network_switch_and_ice } +#endif }; test_suite_t call_test_suite = {