From 260e7e1d5c8b89460cc066bcc8cf2619523e255f Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Tue, 18 Mar 2014 09:08:19 +0100 Subject: [PATCH] minimal SIP UPDATE support --- .cproject | 18 ++++++ coreapi/bellesip_sal/sal_impl.c | 4 +- coreapi/bellesip_sal/sal_impl.h | 1 + coreapi/bellesip_sal/sal_op_call.c | 81 +++++++++++++++++++-------- coreapi/bellesip_sal/sal_op_impl.c | 1 + coreapi/callbacks.c | 32 +++++++---- coreapi/linphonecore.c | 24 +++++--- coreapi/private.h | 2 +- include/sal/sal.h | 2 +- tester/call_tester.c | 88 ++++++++++++++++++++++++++++++ 10 files changed, 208 insertions(+), 45 deletions(-) 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 },