From f4a4a6440bdaccaa997f48b89e3b306c8bd6bc84 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Mon, 8 Sep 2014 18:59:19 +0200 Subject: [PATCH] Support for incoming UPDATEs within dialog. For tests, the possibility to send an UPDATE with linphone_core_update_call() has been added thanks to a $ Added possibility to configure Supported SIP header. --- README.mingw | 2 +- coreapi/bellesip_sal/sal_impl.c | 73 +++++++++++++++++++- coreapi/bellesip_sal/sal_impl.h | 4 +- coreapi/bellesip_sal/sal_op_call.c | 85 +++++++++++------------ coreapi/bellesip_sal/sal_op_impl.c | 6 +- coreapi/callbacks.c | 106 ++++++++++++++++------------- coreapi/chat.c | 1 + coreapi/linphonecall.c | 6 +- coreapi/linphonecore.c | 50 ++++++++++---- coreapi/linphonecore.h | 4 ++ coreapi/private.h | 7 +- coreapi/sal.c | 2 +- coreapi/upnp.c | 2 +- include/sal/sal.h | 12 +++- tester/call_tester.c | 68 ++++++++++++++++++ tester/message_tester.c | 41 +++++++---- 16 files changed, 330 insertions(+), 139 deletions(-) diff --git a/README.mingw b/README.mingw index eaa42b07f..57c0851c4 100644 --- a/README.mingw +++ b/README.mingw @@ -29,7 +29,7 @@ Download lastest linphone-deps-win32 zip from http://download.savannah.gnu.org/releases-noredirect/linphone/misc using your browser. -Download lastest gtk+-2.24.10 win32 _bundle_ from http://www.gtk.org +Download gtk+-2.24.10 win32 _bundle_ from http://www.gtk.org, direct link: http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.24/gtk+-bundle_2.24.10-20120208_win32.zip Install all these three package in /: diff --git a/coreapi/bellesip_sal/sal_impl.c b/coreapi/bellesip_sal/sal_impl.c index 03292db2a..395285eb4 100644 --- a/coreapi/bellesip_sal/sal_impl.c +++ b/coreapi/bellesip_sal/sal_impl.c @@ -534,6 +534,8 @@ void sal_uninit(Sal* sal){ belle_sip_object_unref(sal->prov); belle_sip_object_unref(sal->stack); belle_sip_object_unref(sal->listener); + if (sal->supported) belle_sip_object_unref(sal->supported); + ms_list_free_with_data(sal->supported_tags,ms_free); if (sal->uuid) ms_free(sal->uuid); if (sal->root_ca) ms_free(sal->root_ca); ms_free(sal); @@ -932,10 +934,79 @@ int sal_create_uuid(Sal*ctx, char *uuid, size_t len){ return 0; } +static void make_supported_header(Sal *sal){ + MSList *it; + char *alltags=NULL; + size_t buflen=64; + size_t written=0; + + if (sal->supported){ + belle_sip_object_unref(sal->supported); + sal->supported=NULL; + } + for(it=sal->supported_tags;it!=NULL;it=it->next){ + const char *tag=(const char*)it->data; + size_t taglen=strlen(tag); + if (alltags==NULL || (written+taglen+1>=buflen)) alltags=ms_realloc(alltags,(buflen=buflen*2)); + snprintf(alltags+written,buflen-written,it->next ? "%s, " : "%s",tag); + } + if (alltags){ + sal->supported=belle_sip_header_create("Supported",alltags); + if (sal->supported){ + belle_sip_object_ref(sal->supported); + } + ms_free(alltags); + } +} + +void sal_set_supported_tags(Sal *ctx, const char* tags){ + ctx->supported_tags=ms_list_free_with_data(ctx->supported_tags,ms_free); + if (tags){ + char *iter; + char *buffer=ms_strdup(tags); + char *tag; + char *context=NULL; + iter=buffer; + while((tag=strtok_r(iter,", ",&context))!=NULL){ + iter=NULL; + ctx->supported_tags=ms_list_append(ctx->supported_tags,ms_strdup(tag)); + } + ms_free(buffer); + } + make_supported_header(ctx); +} + +const char *sal_get_supported_tags(Sal *ctx){ + if (ctx->supported){ + return belle_sip_header_get_unparsed_value(ctx->supported); + } + return NULL; +} + +void sal_add_supported_tag(Sal *ctx, const char* tag){ + MSList *elem=ms_list_find_custom(ctx->supported_tags,(MSCompareFunc)strcasecmp,NULL); + if (!elem){ + ctx->supported_tags=ms_list_append(ctx->supported_tags,ms_strdup(tag)); + make_supported_header(ctx); + } + +} + +void sal_remove_supported_tag(Sal *ctx, const char* tag){ + MSList *elem=ms_list_find_custom(ctx->supported_tags,(MSCompareFunc)strcasecmp,NULL); + if (elem){ + ms_free(elem->data); + ctx->supported_tags=ms_list_remove_link(ctx->supported_tags,elem); + make_supported_header(ctx); + } +} + + + belle_sip_response_t* sal_create_response_from_request ( Sal* sal, belle_sip_request_t* req, int code ) { belle_sip_response_t *resp=belle_sip_response_create_from_request(req,code); belle_sip_message_add_header(BELLE_SIP_MESSAGE(resp),BELLE_SIP_HEADER(sal->user_agent)); - belle_sip_message_add_header(BELLE_SIP_MESSAGE(resp),sal_make_supported_header(sal)); + belle_sip_message_add_header(BELLE_SIP_MESSAGE(resp),sal->supported); return resp; } diff --git a/coreapi/bellesip_sal/sal_impl.h b/coreapi/bellesip_sal/sal_impl.h index 613c7b06e..097bbb882 100644 --- a/coreapi/bellesip_sal/sal_impl.h +++ b/coreapi/bellesip_sal/sal_impl.h @@ -39,6 +39,8 @@ struct Sal{ char *root_ca; char *uuid; int refresher_retry_after; /*retry after value for refresher*/ + MSList *supported_tags;/*list of char * */ + belle_sip_header_t *supported; bool_t one_matching_codec; bool_t use_tcp_tls_keep_alive; bool_t nat_helper_enabled; @@ -165,8 +167,6 @@ bool_t sal_op_get_body(SalOp *op, belle_sip_message_t *msg, SalBody *salbody); SalReason sal_reason_to_sip_code(SalReason r); -belle_sip_header_t * sal_make_supported_header(Sal *sal); - void _sal_op_add_custom_headers(SalOp *op, belle_sip_message_t *msg); #endif /* SAL_IMPL_H_ */ diff --git a/coreapi/bellesip_sal/sal_op_call.c b/coreapi/bellesip_sal/sal_op_call.c index 26bbdb038..a2f556c7e 100644 --- a/coreapi/bellesip_sal/sal_op_call.c +++ b/coreapi/bellesip_sal/sal_op_call.c @@ -185,6 +185,7 @@ static void call_process_response(void *op_base, const belle_sip_response_event_ int code = belle_sip_response_get_status_code(response); belle_sip_header_content_type_t *header_content_type=NULL; belle_sip_dialog_t *dialog=belle_sip_response_event_get_dialog(event); + const char *method; if (!client_transaction) { ms_warning("Discarding stateless response [%i] on op [%p]",code,op); @@ -193,13 +194,13 @@ static void call_process_response(void *op_base, const belle_sip_response_event_ req=belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(client_transaction)); set_or_update_dialog(op,dialog); dialog_state=dialog ? belle_sip_dialog_get_state(dialog) : BELLE_SIP_DIALOG_NULL; - + method=belle_sip_request_get_method(req); ms_message("Op [%p] receiving call response [%i], dialog is [%p] in state [%s]",op,code,dialog,belle_sip_dialog_state_to_string(dialog_state)); switch(dialog_state) { case BELLE_SIP_DIALOG_NULL: case BELLE_SIP_DIALOG_EARLY: { - if (strcmp("INVITE",belle_sip_request_get_method(req))==0 ) { + if (strcmp("INVITE",method)==0 ) { if (op->state == SalOpStateTerminating) { /*check if CANCEL was sent before*/ if (strcmp("CANCEL",belle_sip_request_get_method(belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(op->pending_client_trans))))!=0) { @@ -238,28 +239,28 @@ static void call_process_response(void *op_base, const belle_sip_response_event_ case BELLE_SIP_DIALOG_CONFIRMED: { switch (op->state) { case SalOpStateEarly:/*invite case*/ - case SalOpStateActive: /*re-invite case*/ - if (code >=200 - && code<300 - && strcmp("INVITE",belle_sip_request_get_method(req))==0) { - handle_sdp_from_response(op,response); - ack=belle_sip_dialog_create_ack(op->dialog,belle_sip_dialog_get_local_seq_number(op->dialog)); - if (ack==NULL) { - ms_error("This call has been already terminated."); - return ; + case SalOpStateActive: /*re-invite, INFO, UPDATE case*/ + if (strcmp("INVITE",method)==0){ + if (code >=200 && code<300) { + handle_sdp_from_response(op,response); + ack=belle_sip_dialog_create_ack(op->dialog,belle_sip_dialog_get_local_seq_number(op->dialog)); + if (ack==NULL) { + ms_error("This call has been already terminated."); + return ; + } + if (op->sdp_answer){ + set_sdp(BELLE_SIP_MESSAGE(ack),op->sdp_answer); + belle_sip_object_unref(op->sdp_answer); + op->sdp_answer=NULL; + } + belle_sip_dialog_send_ack(op->dialog,ack); + op->base.root->callbacks.call_accepted(op); /*INVITE*/ + op->state=SalOpStateActive; + }else if (code >= 300){ + call_set_error(op,response); } - if (op->sdp_answer){ - set_sdp(BELLE_SIP_MESSAGE(ack),op->sdp_answer); - belle_sip_object_unref(op->sdp_answer); - op->sdp_answer=NULL; - } - belle_sip_dialog_send_ack(op->dialog,ack); - op->base.root->callbacks.call_accepted(op); /*INVITE*/ - op->state=SalOpStateActive; - } else if (code >= 300 && strcmp("INVITE",belle_sip_request_get_method(req))==0){ - call_set_error(op,response); - } else if (code == 491 - && strcmp("INFO",belle_sip_request_get_method(req)) == 0 + }else if (strcmp("INFO",method)==0){ + if (code == 491 && (header_content_type = belle_sip_message_get_header_by_type(req,belle_sip_header_content_type_t)) && strcmp("application",belle_sip_header_content_type_get_type(header_content_type))==0 && strcmp("media_control+xml",belle_sip_header_content_type_get_subtype(header_content_type))==0) { @@ -267,8 +268,11 @@ static void call_process_response(void *op_base, const belle_sip_response_event_ belle_sip_source_t *s=sal_create_timer(op->base.root,vfu_retry,sal_op_ref(op), retry_in, "vfu request retry"); ms_message("Rejected vfu request on op [%p], just retry in [%ui] ms",op,retry_in); belle_sip_object_unref(s); - }else { - /*ignoring*/ + }else { + /*ignoring*/ + } + }else if (strcmp("UPDATE",method)==0){ + op->base.root->callbacks.call_accepted(op); /*INVITE*/ } break; case SalOpStateTerminating: @@ -419,6 +423,7 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t belle_sip_response_t* resp; belle_sip_header_t* call_info; const char *method=belle_sip_request_get_method(req); + bool_t is_update=FALSE; if (strcmp("ACK",method)!=0){ /*ACK does'nt create srv transaction*/ server_transaction = belle_sip_provider_create_server_transaction(op->base.root->prov,belle_sip_request_event_get_request(event)); @@ -490,7 +495,7 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t } 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); + op->base.root->callbacks.call_updating(op,TRUE); } 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); @@ -522,11 +527,11 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t op->base.root->callbacks.call_terminated(op,op->dir==SalOpDirIncoming?sal_op_get_from(op):sal_op_get_to(op)); op->state=SalOpStateTerminating; /*call end not notified by dialog deletion because transaction can end before dialog*/ - } else if(strcmp("INVITE",method)==0) { + } else if(strcmp("INVITE",method)==0 || (is_update=(strcmp("UPDATE",method)==0)) ) { /*re-invite*/ sal_op_reset_descriptions(op); if (process_sdp_for_invite(op,req)==0) - op->base.root->callbacks.call_updating(op); + op->base.root->callbacks.call_updating(op,is_update); } else if (strcmp("INFO",method)==0){ if (belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)) && strstr(belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)),"picture_fast_update")) { @@ -564,22 +569,6 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t belle_sip_server_transaction_send_response(server_transaction,sal_op_create_response_from_request(op,req,481)); } else if (strcmp("MESSAGE",method)==0){ sal_process_incoming_message(op,event); - } else if (strcmp("UPDATE",method)==0) { - - /*FIXME jehan: It might be better to silently accept UPDATE which do not modify either the number or the nature of streams*/ - - /*rfc 3311 - * 5.2 Receiving an UPDATE - * ... - * If the UAS cannot change the session parameters without prompting the user, it SHOULD reject - * the request with a 504 response. - */ - resp=sal_op_create_response_from_request(op,req,504); - belle_sip_response_set_reason_phrase(resp,"Cannot change the session parameters without prompting the user"); - /*belle_sip_message_add_header( BELLE_SIP_MESSAGE(resp) - ,belle_sip_header_create( "Warning", "Cannot change the session parameters without prompting the user"));*/ - belle_sip_server_transaction_send_response(server_transaction,resp); - return; }else{ ms_error("unexpected method [%s] for dialog [%p]",belle_sip_request_get_method(req),op->dialog); unsupported_method(server_transaction,req); @@ -796,13 +785,15 @@ int sal_call_decline(SalOp *op, SalReason reason, const char *redirection /*opti return 0; } -int sal_call_update(SalOp *op, const char *subject){ - +int sal_call_update(SalOp *op, const char *subject, bool_t no_user_consent){ 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"); + if (no_user_consent) + update=belle_sip_dialog_create_request(op->dialog,"UPDATE"); + else + 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 { diff --git a/coreapi/bellesip_sal/sal_op_impl.c b/coreapi/bellesip_sal/sal_op_impl.c index e548cec43..ed18f52d0 100644 --- a/coreapi/bellesip_sal/sal_op_impl.c +++ b/coreapi/bellesip_sal/sal_op_impl.c @@ -118,9 +118,7 @@ belle_sip_header_contact_t* sal_op_create_contact(SalOp *op){ return contact_header; } -belle_sip_header_t * sal_make_supported_header(Sal *sal){ - return belle_sip_header_create("Supported","replaces, outbound"); -} + static void add_initial_route_set(belle_sip_request_t *request, const MSList *list){ const MSList *elem; @@ -201,7 +199,7 @@ belle_sip_request_t* sal_op_build_request(SalOp *op,const char* method) { belle_sip_header_privacy_add_privacy(privacy_header,sal_privacy_to_string(SalPrivacyUser)); belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),BELLE_SIP_HEADER(privacy_header)); } - belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),sal_make_supported_header(op->base.root)); + belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),op->base.root->supported); return req; } diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index a59a47d37..3e4d45d4a 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -503,58 +503,53 @@ static void call_ack(SalOp *op){ } } -static void call_accept_update(LinphoneCore *lc, LinphoneCall *call){ - SalMediaDescription *md; - SalMediaDescription *rmd=sal_call_get_remote_media_description(call->op); - if (rmd!=NULL && call->ice_session!=NULL) { - linphone_core_update_ice_from_remote_media_description(call,rmd); - linphone_core_update_local_media_description_from_ice(call->localdesc,call->ice_session); - } -#ifdef BUILD_UPNP - if(call->upnp_session != NULL) { - linphone_core_update_upnp_from_remote_media_description(call, rmd); - linphone_core_update_local_media_description_from_upnp(call->localdesc,call->upnp_session); - } -#endif //BUILD_UPNP - linphone_call_update_remote_session_id_and_ver(call); - sal_call_accept(call->op); - md=sal_call_get_final_media_description(call->op); - if (md && !sal_media_description_empty(md)){ - linphone_core_update_streams(lc,call,md); - } -} - static void call_resumed(LinphoneCore *lc, LinphoneCall *call){ /*when we are resumed, increment session id, because sdp is changed (a=recvonly disapears)*/ linphone_call_increment_local_media_description(call); - call_accept_update(lc,call); if(lc->vtable.display_status) lc->vtable.display_status(lc,_("We have been resumed.")); - linphone_call_set_state(call,LinphoneCallStreamsRunning,"Connected (streams running)"); + _linphone_core_accept_call_update(lc,call,NULL,LinphoneCallStreamsRunning,"Connected (streams running)"); } static void call_paused_by_remote(LinphoneCore *lc, LinphoneCall *call){ /*when we are resumed, increment session id, because sdp is changed (a=recvonly appears)*/ linphone_call_increment_local_media_description(call); - call_accept_update(lc,call); /* we are being paused */ if(lc->vtable.display_status) lc->vtable.display_status(lc,_("We are paused by other party.")); - linphone_call_set_state (call,LinphoneCallPausedByRemote,"Call paused by remote"); + _linphone_core_accept_call_update(lc,call,NULL,LinphoneCallPausedByRemote,"Call paused by remote"); + } -static void call_updated_by_remote(LinphoneCore *lc, LinphoneCall *call,bool_t notify_application){ +static void call_updated_by_remote(LinphoneCore *lc, LinphoneCall *call, bool_t is_update){ /*first check if media capabilities are compatible*/ - SalMediaDescription* md; - linphone_call_make_local_media_description(lc,call); - sal_call_set_local_media_description(call->op,call->localdesc); - md=sal_call_get_final_media_description(call->op); - if (md && (sal_media_description_empty(md) || linphone_core_incompatible_security(lc,md))){ - sal_call_decline(call->op,SalReasonNotAcceptable,NULL); - return; + SalMediaDescription *md; + SalMediaDescription *rmd=sal_call_get_remote_media_description(call->op); + SalMediaDescription *prev_result_desc=call->resultdesc; + + if (rmd!=NULL){ + if (call->state!=LinphoneCallPaused){ + /*in paused state, we must stay in paused state.*/ + linphone_call_make_local_media_description(lc,call); + sal_call_set_local_media_description(call->op,call->localdesc); + } + md=sal_call_get_final_media_description(call->op); + if (md && (sal_media_description_empty(md) || linphone_core_incompatible_security(lc,md))){ + sal_call_decline(call->op,SalReasonNotAcceptable,NULL); + return; + } + if (is_update && prev_result_desc && md){ + int diff=sal_media_description_equals(prev_result_desc,md); + if (diff & (SAL_MEDIA_DESCRIPTION_CRYPTO_CHANGED|SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED)){ + ms_warning("Cannot accept this update, it is changing parameters that require user approval"); + sal_call_decline(call->op,SalReasonNotAcceptable,NULL); /*FIXME should send 504 Cannot change the session parameters without prompting the user"*/ + return; + } + } } - if (notify_application) { + if (call->state==LinphoneCallStreamsRunning) { + /*reINVITE and in-dialogs UPDATE go here*/ if(lc->vtable.display_status) lc->vtable.display_status(lc,_("Call is updated by remote.")); call->defer_update=FALSE; @@ -562,22 +557,22 @@ static void call_updated_by_remote(LinphoneCore *lc, LinphoneCall *call,bool_t n 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); + if (rmd==NULL) + call->expect_media_in_ack=TRUE; + } else if (is_update){ /*SIP UPDATE case, can occur in early states*/ + _linphone_core_accept_call_update(lc,call,NULL,call->state,linphone_call_state_to_string(call->state)); } } /* this callback is called when an incoming re-INVITE/ SIP UPDATE modifies the session*/ -static void call_updating(SalOp *op){ +static void call_updating(SalOp *op, bool_t is_update){ LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op)); LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op); SalMediaDescription *rmd=sal_call_get_remote_media_description(op); - + if (rmd==NULL){ - /* case of a reINVITE without SDP */ - call_accept_update(lc,call); - call->expect_media_in_ack=TRUE; + /* case of a reINVITE or UPDATE without SDP */ + call_updated_by_remote(lc,call,is_update); return; } @@ -588,19 +583,38 @@ static void call_updating(SalOp *op){ }else call_paused_by_remote(lc,call); break; /*SIP UPDATE CASE*/ + case LinphoneCallOutgoingRinging: case LinphoneCallOutgoingEarlyMedia: - call_updated_by_remote(lc,call,FALSE); + case LinphoneCallIncomingEarlyMedia: + if (is_update) call_updated_by_remote(lc,call,is_update); 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,TRUE); + call_updated_by_remote(lc,call,is_update); } break; - default: - call_accept_update(lc,call); + case LinphoneCallPaused: + call_updated_by_remote(lc,call,is_update); + break; + case LinphoneCallUpdating: + case LinphoneCallPausing: + case LinphoneCallResuming: + case LinphoneCallUpdatedByRemote: + sal_call_decline(call->op,SalReasonNotImplemented,NULL); + /*no break*/ + case LinphoneCallIdle: + case LinphoneCallOutgoingInit: + case LinphoneCallEnd: + case LinphoneCallIncomingReceived: + case LinphoneCallOutgoingProgress: + case LinphoneCallRefered: + case LinphoneCallError: + case LinphoneCallReleased: + ms_warning("Receiving reINVITE or UPDATE while in state [%s], should not happen.",linphone_call_state_to_string(call->state)); + break; } } diff --git a/coreapi/chat.c b/coreapi/chat.c index fa2d38717..3a15ea552 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -635,6 +635,7 @@ void linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessag linphone_address_destroy(addr); msg->storage_id=linphone_chat_message_store(msg); linphone_chat_room_message_received(cr,lc,msg); + linphone_chat_message_unref(msg); ms_free(cleanfrom); ms_free(from); } diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index b02e04fcb..beda9f6ba 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -1424,7 +1424,7 @@ static void video_stream_event_cb(void *user_pointer, const MSFilter *f, const u case MS_VIDEO_DECODER_FIRST_IMAGE_DECODED: ms_message("First video frame decoded successfully"); if (call->nextVideoFrameDecoded._func != NULL) - call->nextVideoFrameDecoded._func(call, call->nextVideoFrameDecoded._user_data); + call->nextVideoFrameDecoded._func(call, call->nextVideoFrameDecoded._user_data); break; case MS_VIDEO_DECODER_SEND_PLI: case MS_VIDEO_DECODER_SEND_SLI: @@ -2766,7 +2766,7 @@ static void handle_ice_events(LinphoneCall *call, OrtpEvent *ev){ linphone_core_start_update_call(call->core, call); break; case LinphoneCallUpdatedByRemote: - linphone_core_start_accept_call_update(call->core, call); + linphone_core_start_accept_call_update(call->core, call,call->prevstate,linphone_call_state_to_string(call->prevstate)); break; case LinphoneCallOutgoingInit: linphone_call_stop_media_streams_for_ice_gathering(call); @@ -2781,7 +2781,7 @@ static void handle_ice_events(LinphoneCall *call, OrtpEvent *ev){ } } else if (evt == ORTP_EVENT_ICE_LOSING_PAIRS_COMPLETED) { if (call->state==LinphoneCallUpdatedByRemote){ - linphone_core_start_accept_call_update(call->core, call); + linphone_core_start_accept_call_update(call->core, call,call->prevstate,linphone_call_state_to_string(call->prevstate)); linphone_core_update_ice_state_in_call_stats(call); } } else if (evt == ORTP_EVENT_ICE_RESTART_NEEDED) { diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 7ef04632c..2bb4143a6 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -464,6 +464,7 @@ static void sip_config_read(LinphoneCore *lc) sal_enable_sip_update_method(lc->sal,lp_config_get_int(lc->config,"sip","sip_update",1)); lc->sip_conf.vfu_with_info=lp_config_get_int(lc->config,"sip","vfu_with_info",1); linphone_core_set_sip_transport_timeout(lc, lp_config_get_int(lc->config, "sip", "transport_timeout", 63000)); + sal_set_supported_tags(lc->sal,lp_config_get_string(lc->config,"sip","supported","replaces, outbound")); } static void rtp_config_read(LinphoneCore *lc) @@ -2853,8 +2854,9 @@ int linphone_core_accept_early_media(LinphoneCore* lc, LinphoneCall* call){ int linphone_core_start_update_call(LinphoneCore *lc, LinphoneCall *call){ const char *subject; + bool_t no_user_consent=call->params.no_user_consent; - linphone_call_make_local_media_description(lc,call); + if (!no_user_consent) linphone_call_make_local_media_description(lc,call); #ifdef BUILD_UPNP if(call->upnp_session != NULL) { linphone_core_update_local_media_description_from_upnp(call->localdesc, call->upnp_session); @@ -2862,8 +2864,10 @@ int linphone_core_start_update_call(LinphoneCore *lc, LinphoneCall *call){ #endif //BUILD_UPNP if (call->params->in_conference){ subject="Conference"; - }else{ + }else if (!no_user_consent){ subject="Media change"; + }else{ + subject="Refreshing"; } if (lc->vtable.display_status) lc->vtable.display_status(lc,_("Modifying call parameters...")); @@ -2872,7 +2876,7 @@ int linphone_core_start_update_call(LinphoneCore *lc, LinphoneCall *call){ /*give a chance to update the contact address if connectivity has changed*/ sal_op_set_contact_address(call->op,sal_op_get_contact_address(call->dest_proxy->op)); }else sal_op_set_contact_address(call->op,NULL); - return sal_call_update(call->op,subject); + return sal_call_update(call->op,subject,no_user_consent); } /** @@ -2984,7 +2988,7 @@ int linphone_core_defer_call_update(LinphoneCore *lc, LinphoneCall *call){ return -1; } -int linphone_core_start_accept_call_update(LinphoneCore *lc, LinphoneCall *call){ +int linphone_core_start_accept_call_update(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState next_state, const char *state_info){ SalMediaDescription *md; if (call->ice_session != NULL) { if (ice_session_nb_losing_pairs(call->ice_session) > 0) { @@ -3002,8 +3006,7 @@ int linphone_core_start_accept_call_update(LinphoneCore *lc, LinphoneCall *call) linphone_core_update_streams (lc,call,md); linphone_call_fix_call_parameters(call); } - 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)"); + linphone_call_set_state(call,next_state,state_info); return 0; } @@ -3032,10 +3035,10 @@ int linphone_core_accept_call_update(LinphoneCore *lc, LinphoneCall *call, const linphone_call_state_to_string(call->state)); return -1; } - return _linphone_core_accept_call_update(lc, call, params); + return _linphone_core_accept_call_update(lc, call, params, call->prevstate, linphone_call_state_to_string(call->prevstate)); } -int _linphone_core_accept_call_update(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallParams *params){ +int _linphone_core_accept_call_update(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallParams *params, LinphoneCallState next_state, const char *state_info){ SalMediaDescription *remote_desc; bool_t keep_sdp_version; #if defined(VIDEO_ENABLED) && defined(BUILD_UPNP) @@ -3048,7 +3051,7 @@ int _linphone_core_accept_call_update(LinphoneCore *lc, LinphoneCall *call, cons /* Remote has sent an INVITE with the same SDP as before, so send a 200 OK with the same SDP as before. */ ms_warning("SDP version has not changed, send same SDP as before."); sal_call_accept(call->op); - linphone_call_set_state(call,LinphoneCallStreamsRunning,"Connected (streams running)"); + linphone_call_set_state(call,next_state,state_info); return 0; } if (params==NULL){ @@ -3086,7 +3089,7 @@ int _linphone_core_accept_call_update(LinphoneCore *lc, LinphoneCall *call, cons } #endif //BUILD_UPNP - linphone_core_start_accept_call_update(lc, call); + linphone_core_start_accept_call_update(lc, call, next_state, state_info); return 0; } @@ -3413,7 +3416,7 @@ int _linphone_core_pause_call(LinphoneCore *lc, LinphoneCall *call) return -1; } sal_call_set_local_media_description(call->op,call->localdesc); - if (sal_call_update(call->op,subject) != 0){ + if (sal_call_update(call->op,subject,FALSE) != 0){ if (lc->vtable.display_warning) lc->vtable.display_warning(lc,_("Could not pause the call")); } @@ -3498,7 +3501,7 @@ int linphone_core_resume_call(LinphoneCore *lc, LinphoneCall *call){ sal_call_set_local_media_description(call->op,call->localdesc); sal_media_description_set_dir(call->localdesc,SalStreamSendRecv); if (call->params->in_conference && !call->current_params->in_conference) subject="Conference"; - if(sal_call_update(call->op,subject) != 0){ + if ( sal_call_update(call->op,subject,FALSE) != 0){ return -1; } linphone_call_set_state(call,LinphoneCallResuming,"Resuming"); @@ -6404,6 +6407,29 @@ void linphone_core_set_file_transfer_server(LinphoneCore *core, const char * ser core->file_transfer_server=ms_strdup(server_url); } +/** + * This function controls signaling features supported by the core. + * They are typically included in a SIP Supported header. + * @param lc the LinphoneCore + * @param tag the feature tag name + * @ingroup initializing +**/ +void linphone_core_add_supported_tag(LinphoneCore *lc, const char *tag){ + sal_add_supported_tag(lc->sal,tag); + lp_config_set_string(lc->config,"sip","supported",sal_get_supported_tags(lc->sal)); +} + +/** + * Remove a supported tag. @see linphone_core_add_supported_tag() + * @param lc the LinphoneCore + * @param tag the tag to remove + * @ingroup initializing +**/ +void linphone_core_remove_supported_tag(LinphoneCore *lc, const char *tag){ + sal_remove_supported_tag(lc->sal,tag); + lp_config_set_string(lc->config,"sip","supported",sal_get_supported_tags(lc->sal)); +} + int linphone_payload_type_get_type(const LinphonePayloadType *pt) { return pt->type; diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index c2af4cdaf..2da0a92d6 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -2861,6 +2861,10 @@ LINPHONE_PUBLIC void linphone_core_set_file_transfer_server(LinphoneCore *core, **/ LINPHONE_PUBLIC const char ** linphone_core_get_supported_file_formats(LinphoneCore *core); +LINPHONE_PUBLIC void linphone_core_add_supported_tag(LinphoneCore *core, const char *tag); + +LINPHONE_PUBLIC void linphone_core_remove_supported_tag(LinphoneCore *core, const char *tag); + #ifdef __cplusplus } #endif diff --git a/coreapi/private.h b/coreapi/private.h index 4fbf1d0e2..89ff1f7a3 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -102,8 +102,9 @@ struct _LinphoneCallParams{ bool_t real_early_media; /*send real media even during early media (for outgoing calls)*/ bool_t in_conference; /*in conference mode */ bool_t low_bandwidth; - LinphonePrivacyMask privacy; + bool_t no_user_consent;/*when set to TRUE an UPDATE request will be used instead of reINVITE*/ uint16_t avpf_rr_interval; + LinphonePrivacyMask privacy; }; BELLE_SIP_DECLARE_VPTR(LinphoneCallParams); @@ -404,7 +405,7 @@ int linphone_core_proceed_with_invite_if_ready(LinphoneCore *lc, LinphoneCall *c int linphone_core_start_invite(LinphoneCore *lc, LinphoneCall *call, const LinphoneAddress* destination/* = NULL if to be taken from the call log */); int linphone_core_restart_invite(LinphoneCore *lc, LinphoneCall *call); int linphone_core_start_update_call(LinphoneCore *lc, LinphoneCall *call); -int linphone_core_start_accept_call_update(LinphoneCore *lc, LinphoneCall *call); +int linphone_core_start_accept_call_update(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState next_state, const char *state_info); void linphone_core_notify_incoming_call(LinphoneCore *lc, LinphoneCall *call); bool_t linphone_core_incompatible_security(LinphoneCore *lc, SalMediaDescription *md); extern SalCallbacks linphone_sal_callbacks; @@ -656,7 +657,7 @@ LinphoneToneDescription *linphone_core_get_call_error_tone(const LinphoneCore *l void linphone_core_play_call_error_tone(LinphoneCore *lc, LinphoneReason reason); void _linphone_core_set_tone(LinphoneCore *lc, LinphoneReason reason, LinphoneToneID id, const char *audiofile); const char *linphone_core_get_tone_file(const LinphoneCore *lc, LinphoneToneID id); -int _linphone_core_accept_call_update(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallParams *params); +int _linphone_core_accept_call_update(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallParams *params, LinphoneCallState next_state, const char *state_info); typedef struct _LinphoneConference LinphoneConference; struct _LinphoneCore diff --git a/coreapi/sal.c b/coreapi/sal.c index ef266bfe7..7af22231b 100644 --- a/coreapi/sal.c +++ b/coreapi/sal.c @@ -307,7 +307,7 @@ int sal_media_description_equals(const SalMediaDescription *md1, const SalMediaD int i; if (strcmp(md1->addr, md2->addr) != 0) result |= SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED; - if (md1->nb_streams != md2->nb_streams) result |= SAL_MEDIA_DESCRIPTION_CODEC_CHANGED; + if (md1->nb_streams != md2->nb_streams) result |= SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED; if (md1->bandwidth != md2->bandwidth) result |= SAL_MEDIA_DESCRIPTION_CODEC_CHANGED; for(i = 0; i < md1->nb_streams; ++i){ result |= sal_stream_description_equals(&md1->streams[i], &md2->streams[i]); diff --git a/coreapi/upnp.c b/coreapi/upnp.c index 562078570..c8082514f 100644 --- a/coreapi/upnp.c +++ b/coreapi/upnp.c @@ -834,7 +834,7 @@ int linphone_upnp_call_process(LinphoneCall *call) { linphone_core_start_update_call(lc, call); break; case LinphoneCallUpdatedByRemote: - linphone_core_start_accept_call_update(lc, call); + linphone_core_start_accept_call_update(lc, call,call->prevstate,linphone_call_state_to_string(call->prevstate)); break; case LinphoneCallOutgoingInit: linphone_core_proceed_with_invite_if_ready(lc, call, NULL); diff --git a/include/sal/sal.h b/include/sal/sal.h index f518aff47..cd087de58 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -72,7 +72,9 @@ typedef enum { #define SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED 0x01 #define SAL_MEDIA_DESCRIPTION_CODEC_CHANGED 0x02 #define SAL_MEDIA_DESCRIPTION_CRYPTO_CHANGED 0x04 -#define SAL_MEDIA_DESCRIPTION_CHANGED (SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED | SAL_MEDIA_DESCRIPTION_CODEC_CHANGED | SAL_MEDIA_DESCRIPTION_CRYPTO_CHANGED) +#define SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED 0x08 +#define SAL_MEDIA_DESCRIPTION_CHANGED (SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED | SAL_MEDIA_DESCRIPTION_CODEC_CHANGED |\ + SAL_MEDIA_DESCRIPTION_CRYPTO_CHANGED |SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED) const char* sal_transport_to_string(SalTransport transport); SalTransport sal_transport_parse(const char*); @@ -413,7 +415,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/UPDATE is received*/ +typedef void (*SalOnCallUpdating)(SalOp *op, bool_t is_update);/*< Called when a reINVITE/UPDATE is received*/ typedef void (*SalOnCallTerminated)(SalOp *op, const char *from); typedef void (*SalOnCallFailure)(SalOp *op); typedef void (*SalOnCallReleased)(SalOp *salop); @@ -517,6 +519,10 @@ int sal_get_listening_port(Sal *ctx, SalTransport tr); int sal_unlisten_ports(Sal *ctx); int sal_transport_available(Sal *ctx, SalTransport t); void sal_set_dscp(Sal *ctx, int dscp); +void sal_set_supported_tags(Sal *ctx, const char* tags); +void sal_add_supported_tag(Sal *ctx, const char* tag); +void sal_remove_supported_tag(Sal *ctx, const char* tag); +const char *sal_get_supported_tags(Sal *ctx); int sal_reset_transports(Sal *ctx); ortp_socket_t sal_get_socket(Sal *ctx); void sal_set_user_agent(Sal *ctx, const char *user_agent); @@ -615,7 +621,7 @@ int sal_call_notify_ringing(SalOp *h, bool_t early_media); /*accept an incoming call or, during a call accept a reINVITE*/ 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); +int sal_call_update(SalOp *h, const char *subject, bool_t no_user_consent); SalMediaDescription * sal_call_get_remote_media_description(SalOp *h); SalMediaDescription * sal_call_get_final_media_description(SalOp *h); int sal_call_refer(SalOp *h, const char *refer_to); diff --git a/tester/call_tester.c b/tester/call_tester.c index 9313c6f74..c8de2ee80 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -2908,6 +2908,72 @@ static void video_call_snapshot(void) { #endif +static void call_with_in_dialog_update(void) { + int begin; + int leaked_objects; + LinphoneCoreManager* marie; + LinphoneCoreManager* pauline; + LinphoneCallParams *params; + + belle_sip_object_enable_leak_detector(TRUE); + begin=belle_sip_object_get_object_count(); + + marie = linphone_core_manager_new( "marie_rc"); + pauline = linphone_core_manager_new( "pauline_rc"); + CU_ASSERT_TRUE(call(pauline,marie)); + liblinphone_tester_check_rtcp(marie,pauline); + params=linphone_core_create_call_params(marie->lc,linphone_core_get_current_call(marie->lc)); + params->no_user_consent=TRUE; + linphone_core_update_call(marie->lc,linphone_core_get_current_call(marie->lc),params); + CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&marie->stat.number_of_LinphoneCallUpdating,1)); + CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&marie->stat.number_of_LinphoneCallStreamsRunning,2)); + CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&pauline->stat.number_of_LinphoneCallUpdatedByRemote,1)); + CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&pauline->stat.number_of_LinphoneCallStreamsRunning,2)); + end_call(marie,pauline); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); + + leaked_objects=belle_sip_object_get_object_count()-begin; + CU_ASSERT_TRUE(leaked_objects==0); + if (leaked_objects>0){ + belle_sip_object_dump_active_objects(); + } +} + +static void call_with_custom_supported_tags(void) { + int begin; + int leaked_objects; + LinphoneCoreManager* marie; + LinphoneCoreManager* pauline; + const LinphoneCallParams *remote_params; + const char *recv_supported; + + belle_sip_object_enable_leak_detector(TRUE); + begin=belle_sip_object_get_object_count(); + + marie = linphone_core_manager_new( "marie_rc"); + pauline = linphone_core_manager_new( "pauline_rc"); + + linphone_core_add_supported_tag(marie->lc,"pouet-tag"); + CU_ASSERT_TRUE(call(pauline,marie)); + liblinphone_tester_check_rtcp(marie,pauline); + remote_params=linphone_call_get_remote_params(linphone_core_get_current_call(pauline->lc)); + recv_supported=linphone_call_params_get_custom_header(remote_params,"supported"); + CU_ASSERT_PTR_NOT_NULL(recv_supported); + if (recv_supported){ + CU_ASSERT_TRUE(strstr(recv_supported,"pouet-tag")!=NULL); + } + end_call(marie,pauline); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); + + leaked_objects=belle_sip_object_get_object_count()-begin; + CU_ASSERT_TRUE(leaked_objects==0); + if (leaked_objects>0){ + belle_sip_object_dump_active_objects(); + } +} + test_t call_tests[] = { { "Early declined call", early_declined_call }, { "Call declined", call_declined }, @@ -3002,6 +3068,8 @@ test_t call_tests[] = { { "SAVPF to AVPF call", savpf_to_avpf_call }, { "SAVPF to SAVP call", savpf_to_savp_call }, { "SAVPF to SAVPF call", savpf_to_savpf_call }, + { "Call with in-dialog UPDATE request", call_with_in_dialog_update }, + { "Call with custom supported tags", call_with_custom_supported_tags } }; test_suite_t call_test_suite = { diff --git a/tester/message_tester.c b/tester/message_tester.c index 13081bd18..f327a62e9 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -331,23 +331,34 @@ static void text_message_compatibility_mode(void) { } static void text_message_with_ack(void) { - LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); - LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); - char* to = linphone_address_as_string(marie->identity); - LinphoneChatRoom* chat_room = linphone_core_create_chat_room(pauline->lc,to); - LinphoneChatMessage* message = linphone_chat_room_create_message(chat_room,"Bli bli bli \n blu"); + belle_sip_object_enable_leak_detector(TRUE); + int begin=belle_sip_object_get_object_count(); + int leaked_objects; + { - int dummy=0; - wait_for_until(marie->lc,pauline->lc,&dummy,1,100); /*just to have time to purge message stored in the server*/ - reset_counters(&marie->stat); - reset_counters(&pauline->stat); + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + char* to = linphone_address_as_string(marie->identity); + LinphoneChatRoom* chat_room = linphone_core_create_chat_room(pauline->lc,to); + LinphoneChatMessage* message = linphone_chat_room_create_message(chat_room,"Bli bli bli \n blu"); + { + int dummy=0; + wait_for_until(marie->lc,pauline->lc,&dummy,1,100); /*just to have time to purge message stored in the server*/ + reset_counters(&marie->stat); + reset_counters(&pauline->stat); + } + linphone_chat_room_send_message2(chat_room,message,liblinphone_tester_chat_message_state_change,pauline->lc); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageReceived,1)); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneMessageDelivered,1)); + CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageInProgress,1); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); + } + leaked_objects=belle_sip_object_get_object_count()-begin; + CU_ASSERT_TRUE(leaked_objects==0); + if (leaked_objects>0){ + belle_sip_object_dump_active_objects(); } - linphone_chat_room_send_message2(chat_room,message,liblinphone_tester_chat_message_state_change,pauline->lc); - CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageReceived,1)); - CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneMessageDelivered,1)); - CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageInProgress,1); - linphone_core_manager_destroy(marie); - linphone_core_manager_destroy(pauline); } static void text_message_with_external_body(void) {