From 4a2bb40b1b13c9aa299c3dea74952721c21fd5ac Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Fri, 16 May 2014 11:15:55 +0200 Subject: [PATCH 01/94] Upcase SQL statements for reading convenience --- coreapi/message_storage.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index c7cc86e71..c46980d57 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -149,7 +149,7 @@ unsigned int linphone_chat_message_store(LinphoneChatMessage *msg){ char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(msg->chat_room)); char *local_contact=linphone_address_as_string_uri_only(linphone_chat_message_get_local_address(msg)); char datebuf[26]; - char *buf=sqlite3_mprintf("insert into history values(NULL,%Q,%Q,%i,%Q,%Q,%i,%i,%Q);", + char *buf=sqlite3_mprintf("INSERT INTO history VALUES(NULL,%Q,%Q,%i,%Q,%Q,%i,%i,%Q);", local_contact,peer,msg->dir,msg->message,my_ctime_r(&msg->time,datebuf),msg->is_read,msg->state,msg->external_body_url); linphone_sql_request(lc->db,buf); sqlite3_free(buf); @@ -164,7 +164,7 @@ void linphone_chat_message_store_state(LinphoneChatMessage *msg){ LinphoneCore *lc=msg->chat_room->lc; if (lc->db){ char time_str[26]; - char *buf=sqlite3_mprintf("update history set status=%i where message = %Q and time = %Q;", + char *buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE message = %Q AND time = %Q;", msg->state,msg->message,my_ctime_r(&msg->time,time_str)); linphone_sql_request(lc->db,buf); sqlite3_free(buf); @@ -187,7 +187,7 @@ void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){ if (lc->db==NULL) return ; char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); - char *buf=sqlite3_mprintf("update history set read=%i where remoteContact = %Q;", + char *buf=sqlite3_mprintf("UPDATE history SET read=%i WHERE remoteContact = %Q;", read,peer); linphone_sql_request(lc->db,buf); sqlite3_free(buf); @@ -199,7 +199,7 @@ void linphone_chat_room_update_url(LinphoneChatRoom *cr, LinphoneChatMessage *ms if (lc->db==NULL) return ; - char *buf=sqlite3_mprintf("update history set url=%Q where id=%i;",msg->external_body_url,msg->storage_id); + char *buf=sqlite3_mprintf("UPDATE history SET url=%Q WHERE id=%i;",msg->external_body_url,msg->storage_id); linphone_sql_request(lc->db,buf); sqlite3_free(buf); } @@ -211,7 +211,7 @@ int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){ if (lc->db==NULL) return 0; char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); - char *buf=sqlite3_mprintf("select count(*) from history where remoteContact = %Q and read = 0;",peer); + char *buf=sqlite3_mprintf("SELECT count(*) FROM history WHERE remoteContact = %Q AND read = 0;",peer); sqlite3_stmt *selectStatement; int returnValue = sqlite3_prepare_v2(lc->db,buf,-1,&selectStatement,NULL); if (returnValue == SQLITE_OK){ @@ -230,7 +230,7 @@ void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage if (lc->db==NULL) return ; - char *buf=sqlite3_mprintf("delete from history where id = %i;", msg->storage_id); + char *buf=sqlite3_mprintf("DELETE FROM history WHERE id = %i;", msg->storage_id); linphone_sql_request(lc->db,buf); sqlite3_free(buf); } @@ -241,7 +241,7 @@ void linphone_chat_room_delete_history(LinphoneChatRoom *cr){ if (lc->db==NULL) return ; char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); - char *buf=sqlite3_mprintf("delete from history where remoteContact = %Q;",peer); + char *buf=sqlite3_mprintf("DELETE FROM history WHERE remoteContact = %Q;",peer); linphone_sql_request(lc->db,buf); sqlite3_free(buf); ms_free(peer); @@ -257,9 +257,9 @@ MSList *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){ peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); cr->messages_hist = NULL; if (nb_message > 0) - buf=sqlite3_mprintf("select * from history where remoteContact = %Q order by id DESC limit %i ;",peer,nb_message); + buf=sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC LIMIT %i ;",peer,nb_message); else - buf=sqlite3_mprintf("select * from history where remoteContact = %Q order by id DESC;",peer); + buf=sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC;",peer); linphone_sql_request_message(lc->db,buf,cr); sqlite3_free(buf); ret=cr->messages_hist; @@ -275,7 +275,7 @@ void linphone_close_storage(sqlite3* db){ void linphone_create_table(sqlite3* db){ char* errmsg=NULL; int ret; - ret=sqlite3_exec(db,"CREATE TABLE if not exists history (id INTEGER PRIMARY KEY AUTOINCREMENT, localContact TEXT NOT NULL, remoteContact TEXT NOT NULL, direction INTEGER, message TEXT, time TEXT NOT NULL, read INTEGER, status INTEGER);", + ret=sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY AUTOINCREMENT, localContact TEXT NOT NULL, remoteContact TEXT NOT NULL, direction INTEGER, message TEXT, time TEXT NOT NULL, read INTEGER, status INTEGER);", 0,0,&errmsg); if(ret != SQLITE_OK) { ms_error("Error in creation: %s.\n", errmsg); @@ -299,7 +299,7 @@ void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) { char *buf; if (lc->db==NULL) return; - buf=sqlite3_mprintf("SELECT remoteContact FROM history Group By remoteContact;"); + buf=sqlite3_mprintf("SELECT remoteContact FROM history GROUP BY remoteContact;"); linphone_sql_request_all(lc->db,buf,lc); sqlite3_free(buf); } From d491197c914e8921339c7893ddfab0fb46f3a323 Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Mon, 19 May 2014 14:12:35 +0200 Subject: [PATCH 02/94] Switch to UTC timestamp storage for messages, instead of string-based local timezone time. Migration procedure included. --- coreapi/message_storage.c | 126 +++++++++++++++++++++++++++++--------- 1 file changed, 98 insertions(+), 28 deletions(-) diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index c46980d57..434b7e484 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -35,10 +35,6 @@ static inline char *my_ctime_r(const time_t *t, char *buf){ #include "sqlite3.h" -static const char *days[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; -static const char *months[]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; - - static inline LinphoneChatMessage* get_transient_message(LinphoneChatRoom* cr, unsigned int storage_id){ MSList* transients = cr->transient_messages; LinphoneChatMessage* chat; @@ -55,9 +51,7 @@ static inline LinphoneChatMessage* get_transient_message(LinphoneChatRoom* cr, u static void create_chat_message(char **argv, void *data){ LinphoneChatRoom *cr = (LinphoneChatRoom *)data; LinphoneAddress *from; - struct tm ret={0}; - char tmp1[80]={0}; - char tmp2[80]={0}; + unsigned int storage_id = atoi(argv[0]); // check if the message exists in the transient list, in which case we should return that one. @@ -75,20 +69,12 @@ static void create_chat_message(char **argv, void *data){ linphone_chat_message_set_from(new_message,from); linphone_address_destroy(from); - if(argv[5]!=NULL){ - int i,j; - sscanf(argv[5],"%3c %3c%d%d:%d:%d %d",tmp1,tmp2,&ret.tm_mday, - &ret.tm_hour,&ret.tm_min,&ret.tm_sec,&ret.tm_year); - ret.tm_year-=1900; - for(i=0;i<7;i++) { - if(strcmp(tmp1,days[i])==0) ret.tm_wday=i; - } - for(j=0;j<12;j++) { - if(strcmp(tmp2,months[j])==0) ret.tm_mon=j; - } - ret.tm_isdst=-1; + if( argv[9] != NULL ){ + new_message->time = (time_t)atol(argv[9]); + } else { + new_message->time = time(NULL); } - new_message->time=argv[5]!=NULL ? mktime(&ret) : time(NULL); + new_message->is_read=atoi(argv[6]); new_message->state=atoi(argv[7]); new_message->storage_id=storage_id; @@ -148,9 +134,16 @@ unsigned int linphone_chat_message_store(LinphoneChatMessage *msg){ if (lc->db){ char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(msg->chat_room)); char *local_contact=linphone_address_as_string_uri_only(linphone_chat_message_get_local_address(msg)); - char datebuf[26]; - char *buf=sqlite3_mprintf("INSERT INTO history VALUES(NULL,%Q,%Q,%i,%Q,%Q,%i,%i,%Q);", - local_contact,peer,msg->dir,msg->message,my_ctime_r(&msg->time,datebuf),msg->is_read,msg->state,msg->external_body_url); + char *buf=sqlite3_mprintf("INSERT INTO history VALUES(NULL,%Q,%Q,%i,%Q,%Q,%i,%i,%Q,%i);", + local_contact, + peer, + msg->dir, + msg->message, + "-1", /* use UTC field now */ + msg->is_read, + msg->state, + msg->external_body_url, + msg->time); linphone_sql_request(lc->db,buf); sqlite3_free(buf); ms_free(local_contact); @@ -163,9 +156,8 @@ unsigned int linphone_chat_message_store(LinphoneChatMessage *msg){ void linphone_chat_message_store_state(LinphoneChatMessage *msg){ LinphoneCore *lc=msg->chat_room->lc; if (lc->db){ - char time_str[26]; - char *buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE message = %Q AND time = %Q;", - msg->state,msg->message,my_ctime_r(&msg->time,time_str)); + char *buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE message = %Q AND utc = %i;", + msg->state,msg->message,msg->time); linphone_sql_request(lc->db,buf); sqlite3_free(buf); @@ -275,7 +267,16 @@ void linphone_close_storage(sqlite3* db){ void linphone_create_table(sqlite3* db){ char* errmsg=NULL; int ret; - ret=sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY AUTOINCREMENT, localContact TEXT NOT NULL, remoteContact TEXT NOT NULL, direction INTEGER, message TEXT, time TEXT NOT NULL, read INTEGER, status INTEGER);", + ret=sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS history (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "localContact TEXT NOT NULL," + "remoteContact TEXT NOT NULL," + "direction INTEGER," + "message TEXT," + "time TEXT NOT NULL," + "read INTEGER," + "status INTEGER" + ");", 0,0,&errmsg); if(ret != SQLITE_OK) { ms_error("Error in creation: %s.\n", errmsg); @@ -283,16 +284,85 @@ void linphone_create_table(sqlite3* db){ } } + +static const char *days[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; +static const char *months[]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; +static time_t parse_time_from_db( const char* time ){ + /* messages used to be stored in the DB by using string-based time */ + struct tm ret={0}; + char tmp1[80]={0}; + char tmp2[80]={0}; + int i,j; + time_t parsed = 0; + + if( sscanf(time,"%3c %3c%d%d:%d:%d %d",tmp1,tmp2,&ret.tm_mday, + &ret.tm_hour,&ret.tm_min,&ret.tm_sec,&ret.tm_year) == 7 ){ + ret.tm_year-=1900; + for(i=0;i<7;i++) { + if(strcmp(tmp1,days[i])==0) ret.tm_wday=i; + } + for(j=0;j<12;j++) { + if(strcmp(tmp2,months[j])==0) ret.tm_mon=j; + } + ret.tm_isdst=-1; + parsed = mktime(&ret); + } + return parsed; +} + + +static int migrate_messages(void* data,int argc, char** argv, char** column_names) { + time_t new_time = parse_time_from_db(argv[1]); + if( new_time ){ + /* replace 'time' by -1 and set 'utc' to the timestamp */ + char *buf = sqlite3_mprintf("UPDATE history SET utc=%i,time='-1' WHERE id=%i", new_time, atoi(argv[0])); + if( buf) { + linphone_sql_request((sqlite3*)data, buf); + sqlite3_free(buf); + } + } else { + printf("Cannot parse time %s from id %s", argv[1], argv[0]); + } + return 0; +} + +static void linphone_migrate_timestamps(sqlite3* db){ + int ret; + char* errmsg = NULL; + + ret = sqlite3_exec(db,"SELECT id,time,direction FROM history WHERE time != '-1'", migrate_messages, db, &errmsg); + if( ret != SQLITE_OK ){ + printf("Error migrating outgoing messages: %s.\n", errmsg); + sqlite3_free(errmsg); + } else { + printf("Migrated message timestamps to UTC\n"); + } +} + void linphone_update_table(sqlite3* db) { char* errmsg=NULL; int ret; + + // for image url storage ret=sqlite3_exec(db,"ALTER TABLE history ADD COLUMN url TEXT;",NULL,NULL,&errmsg); if(ret != SQLITE_OK) { ms_warning("Table already up to date: %s.\n", errmsg); sqlite3_free(errmsg); } else { - ms_debug("Table updated successfuly."); + ms_debug("Table updated successfully for URL."); } + + // for UTC timestamp storage + ret = sqlite3_exec(db, "ALTER TABLE history ADD COLUMN utc INTEGER;", NULL,NULL,&errmsg); + if( ret != SQLITE_OK ){ + ms_warning("Table already up to date: %s.\n", errmsg); + sqlite3_free(errmsg); + } else { + ms_debug("Table updated successfully for UTC."); + } + + // migrate from old text-based timestamps to unix time-based timestamps + linphone_migrate_timestamps(db); } void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) { From f0d7040fbf1cc8d9cf6c441937966b5c8bc8d3d0 Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Tue, 20 May 2014 10:24:05 +0200 Subject: [PATCH 03/94] unit tests for transient messages --- tester/message_tester.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tester/message_tester.c b/tester/message_tester.c index 8568492d4..6aa884616 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -234,12 +234,19 @@ static void text_message_with_external_body(void) { LinphoneChatMessage* message = linphone_chat_room_create_message(chat_room,"Bli bli bli \n blu"); linphone_chat_message_set_external_body_url(message,message_external_body_url="http://www.linphone.org"); linphone_chat_room_send_message2(chat_room,message,liblinphone_tester_chat_message_state_change,pauline->lc); + + /* check transient message list: the message should be in it, and should be the only one */ + CU_ASSERT_EQUAL(ms_list_size(chat_room->transient_messages), 1); + CU_ASSERT_EQUAL(ms_list_nth_data(chat_room->transient_messages,0), message); + 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); CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneMessageExtBodyReceived,1); + CU_ASSERT_EQUAL(ms_list_size(chat_room->transient_messages), 0); + linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); } @@ -254,10 +261,18 @@ static void text_message_with_send_error(void) { sal_set_send_error(marie->lc->sal, -1); linphone_chat_room_send_message2(chat_room,message,liblinphone_tester_chat_message_state_change,marie->lc); + /* check transient message list: the message should be in it, and should be the only one */ + CU_ASSERT_EQUAL(ms_list_size(chat_room->transient_messages), 1); + CU_ASSERT_EQUAL(ms_list_nth_data(chat_room->transient_messages,0), message); + + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageNotDelivered,1)); /*CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneMessageInProgress,1);*/ CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageReceived,0); + /* the message should have been discarded from transient list after an error */ + CU_ASSERT_EQUAL(ms_list_size(chat_room->transient_messages), 0); + sal_set_send_error(marie->lc->sal, 0); linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); From 20c646bc4a8fce78727ac049fcfdeb90bab1b070 Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Tue, 20 May 2014 10:49:34 +0200 Subject: [PATCH 04/94] Allow multiple calls to linphone_core_message_storage_init --- coreapi/message_storage.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index 434b7e484..8eff460dd 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -87,7 +87,7 @@ static void create_chat_message(char **argv, void *data){ static int callback_all(void *data, int argc, char **argv, char **colName){ LinphoneCore* lc = (LinphoneCore*) data; char* address = argv[0]; - linphone_core_create_chat_room(lc, address); + linphone_core_get_or_create_chat_room(lc, address); return 0; } @@ -378,6 +378,9 @@ void linphone_core_message_storage_init(LinphoneCore *lc){ int ret; const char *errmsg; sqlite3 *db; + + linphone_core_message_storage_close(lc); + ret=sqlite3_open(lc->chat_db_file,&db); if(ret != SQLITE_OK) { errmsg=sqlite3_errmsg(db); From 0dce1b41dbbbe6e3e236cfc8251637b3e89ac2ed Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Tue, 20 May 2014 11:16:38 +0200 Subject: [PATCH 05/94] Don't use printf --- coreapi/message_storage.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index 8eff460dd..7b15c15a0 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -321,7 +321,7 @@ static int migrate_messages(void* data,int argc, char** argv, char** column_name sqlite3_free(buf); } } else { - printf("Cannot parse time %s from id %s", argv[1], argv[0]); + ms_warning("Cannot parse time %s from id %s\n", argv[1], argv[0]); } return 0; } @@ -332,10 +332,10 @@ static void linphone_migrate_timestamps(sqlite3* db){ ret = sqlite3_exec(db,"SELECT id,time,direction FROM history WHERE time != '-1'", migrate_messages, db, &errmsg); if( ret != SQLITE_OK ){ - printf("Error migrating outgoing messages: %s.\n", errmsg); + ms_warning("Error migrating outgoing messages: %s.\n", errmsg); sqlite3_free(errmsg); } else { - printf("Migrated message timestamps to UTC\n"); + ms_message("Migrated message timestamps to UTC\n"); } } From a5e9eff708a8ef767c8417280cd5de21e75a7d8b Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 20 May 2014 14:01:21 +0200 Subject: [PATCH 06/94] Update oRTP and ms2 submodules. --- mediastreamer2 | 2 +- oRTP | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mediastreamer2 b/mediastreamer2 index 77022250a..ad376cf21 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 77022250a04459648101c3b4152d83158bbe0e63 +Subproject commit ad376cf215143507c1f8e297084e210939e7f31b diff --git a/oRTP b/oRTP index 519fabfed..d8b09cdb5 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 519fabfed1b38c9cde977d4c7a8a7c2bb642e0cb +Subproject commit d8b09cdb524ba1c0eaaeefdca79d5d6daebc19f8 From 93de3b194641cd902492a1ea0656a3ccaa5a660d Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 20 May 2014 16:21:39 +0200 Subject: [PATCH 07/94] Update oRTP submodule. --- oRTP | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oRTP b/oRTP index d8b09cdb5..73317c9bf 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit d8b09cdb524ba1c0eaaeefdca79d5d6daebc19f8 +Subproject commit 73317c9bf59700b44f85008f6e254476bbe756eb From bb6d660594e745c7a2f409b362697e5a5def80d0 Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Tue, 20 May 2014 18:38:46 +0200 Subject: [PATCH 08/94] rework proxy config management edit()/done() method to only send unregister message when really needed --- coreapi/linphonecore.c | 2 +- coreapi/private.h | 9 +++ coreapi/proxy.c | 112 +++++++++++++++++++++++---- include/sal/sal.h | 3 + tester/register_tester.c | 162 ++++++++++++++++++++++++++++++++++++--- tester/setup_tester.c | 62 ++++++++++++++- 6 files changed, 321 insertions(+), 29 deletions(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index a77d550ae..157ce6acf 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -5664,7 +5664,7 @@ void sip_config_uninit(LinphoneCore *lc) if (lc->network_reachable) { for(elem=config->proxies;elem!=NULL;elem=ms_list_next(elem)){ LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)(elem->data); - linphone_proxy_config_edit(cfg); /* to unregister */ + _linphone_proxy_config_unregister(cfg); /* to unregister without changing the stored flag enable_register */ } ms_message("Unregistration started."); diff --git a/coreapi/private.h b/coreapi/private.h index e3a97b8e5..ae1e109bd 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -393,6 +393,9 @@ LinphoneProxyConfig * is_a_linphone_proxy_config(void *user_pointer); void linphone_core_queue_task(LinphoneCore *lc, belle_sip_source_func_t task_fun, void *data, const char *task_description); static const int linphone_proxy_config_magic=0x7979; +bool_t linphone_proxy_config_address_equal(const LinphoneAddress *a, const LinphoneAddress *b); +bool_t linphone_proxy_config_is_server_config_changed(const LinphoneProxyConfig* obj); +void _linphone_proxy_config_unregister(LinphoneProxyConfig *obj); /*chat*/ void linphone_chat_message_destroy(LinphoneChatMessage* msg); @@ -428,6 +431,12 @@ struct _LinphoneProxyConfig void* user_data; time_t deletion_date; LinphonePrivacyMask privacy; + /*use to check if server config has changed between edit() and done()*/ + LinphoneAddress *saved_proxy; + LinphoneAddress *saved_identity; + LinphoneAddress *saved_route; + /*---*/ + }; struct _LinphoneAuthInfo diff --git a/coreapi/proxy.c b/coreapi/proxy.c index 7827dcb82..a71ea868a 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -26,6 +26,60 @@ Copyright (C) 2000 Simon MORLAT (simon.morlat@linphone.org) #include +/*store current config related to server location*/ +static void linphone_proxy_config_store_server_config(LinphoneProxyConfig* obj) { + if (obj->saved_identity) linphone_address_destroy(obj->saved_identity); + if (obj->reg_identity) + obj->saved_identity = linphone_address_new(obj->reg_identity); + else + obj->saved_identity = NULL; + + if (obj->saved_proxy) linphone_address_destroy(obj->saved_proxy); + if (obj->reg_proxy) + obj->saved_proxy = linphone_address_new(obj->reg_proxy); + else + obj->saved_proxy = NULL; + + if (obj->saved_route) linphone_address_destroy(obj->saved_route); + if (obj->reg_route) + obj->saved_route = linphone_address_new(obj->reg_route); + else + obj->saved_route = NULL; +} + +bool_t linphone_proxy_config_address_equal(const LinphoneAddress *a, const LinphoneAddress *b) { + if (a == NULL && b == NULL) + return TRUE; + else if (!a || !b) + return FALSE; + + if (linphone_address_weak_equal(a,b)) { + /*also check both transport and uri */ + if (!(linphone_address_is_secure(a) ^ linphone_address_is_secure(b))) { + return linphone_address_get_transport(a) == linphone_address_get_transport(b); + } else + return FALSE; /*secure flag not equals*/ + } else + return FALSE; /*either username, domain or port ar not equals*/ + +} + +bool_t linphone_proxy_config_is_server_config_changed(const LinphoneProxyConfig* obj) { + LinphoneAddress *current_identity=obj->reg_identity?linphone_address_new(obj->reg_identity):NULL; + LinphoneAddress *current_proxy=obj->reg_proxy?linphone_address_new(obj->reg_proxy):NULL; + LinphoneAddress *current_route=obj->reg_route?linphone_address_new(obj->reg_route):NULL; + + if (!linphone_proxy_config_address_equal(obj->saved_identity,current_identity)) + return TRUE; + + if (!linphone_proxy_config_address_equal(obj->saved_proxy,current_proxy)) + return TRUE; + + if (!linphone_proxy_config_address_equal(obj->saved_route,current_route)) + return TRUE; + + return FALSE; +} void linphone_proxy_config_write_all_to_config_file(LinphoneCore *lc){ MSList *elem; @@ -106,6 +160,9 @@ void linphone_proxy_config_destroy(LinphoneProxyConfig *obj){ if (obj->publish_op) sal_op_release(obj->publish_op); if (obj->contact_params) ms_free(obj->contact_params); if (obj->contact_uri_params) ms_free(obj->contact_uri_params); + if (obj->saved_proxy!=NULL) linphone_address_destroy(obj->saved_proxy); + if (obj->saved_identity!=NULL) ms_free(obj->saved_identity); + if (obj->saved_route!=NULL) ms_free(obj->saved_route); ms_free(obj); } @@ -265,21 +322,16 @@ void linphone_proxy_config_enable_publish(LinphoneProxyConfig *obj, bool_t val){ **/ void linphone_proxy_config_edit(LinphoneProxyConfig *obj){ if (obj->publish && obj->publish_op){ - /*unpublish*/ - sal_publish_presence(obj->publish_op,NULL,NULL,0,(SalPresenceModel *)NULL); - sal_op_release(obj->publish_op); - obj->publish_op=NULL; - } - if (obj->reg_sendregister){ - /* unregister */ - if (obj->state == LinphoneRegistrationOk - || obj->state == LinphoneRegistrationProgress) { - sal_unregister(obj->op); - } else { - /*stop refresher*/ - if (obj->op) sal_op_stop_refreshing(obj->op); - } + /*unpublish*/ + sal_publish_presence(obj->publish_op,NULL,NULL,0,(SalPresenceModel *)NULL); + sal_op_release(obj->publish_op); + obj->publish_op=NULL; } + /*store current config related to server location*/ + linphone_proxy_config_store_server_config(obj); + + /*stop refresher in any case*/ + if (obj->op) sal_op_stop_refreshing(obj->op); } void linphone_proxy_config_apply(LinphoneProxyConfig *obj,LinphoneCore *lc){ @@ -336,6 +388,14 @@ LinphoneAddress *guess_contact_for_register(LinphoneProxyConfig *obj){ return ret; } +/** + * unregister without moving the register_enable flag + */ +void _linphone_proxy_config_unregister(LinphoneProxyConfig *obj) { + if (obj->state == LinphoneRegistrationOk) { + sal_unregister(obj->op); + } +} static void linphone_proxy_config_register(LinphoneProxyConfig *obj){ if (obj->reg_sendregister){ @@ -360,8 +420,14 @@ static void linphone_proxy_config_register(LinphoneProxyConfig *obj){ } ms_free(proxy_string); } else { - /*stop refresher, just in case*/ - if (obj->op) sal_op_stop_refreshing(obj->op); + /* unregister if registered*/ + if (obj->state == LinphoneRegistrationProgress) { + linphone_proxy_config_set_state(obj,LinphoneRegistrationCleared,"Registration cleared"); + } + _linphone_proxy_config_unregister(obj); + + + } } @@ -843,7 +909,19 @@ int linphone_proxy_config_normalize_number(LinphoneProxyConfig *proxy, const cha **/ int linphone_proxy_config_done(LinphoneProxyConfig *obj) { - if (!linphone_proxy_config_check(obj->lc,obj)) return -1; + if (!linphone_proxy_config_check(obj->lc,obj)) + return -1; + + /*check if server address as changed*/ + if (linphone_proxy_config_is_server_config_changed(obj)) { + /* server config has changed, need to unregister from previous first*/ + if (obj->op) { + _linphone_proxy_config_unregister(obj); + sal_op_set_user_pointer(obj->op,NULL); /*we don't want to receive status for this un register*/ + sal_op_unref(obj->op); /*but we keep refresher to handle authentication if needed*/ + obj->op=NULL; + } + } obj->commit=TRUE; linphone_proxy_config_write_all_to_config_file(obj->lc); return 0; diff --git a/include/sal/sal.h b/include/sal/sal.h index 2d7e4eec8..55c5ebf20 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -551,6 +551,9 @@ void sal_op_set_to_address(SalOp *op, const SalAddress *to); SalOp *sal_op_ref(SalOp* h); void sal_op_stop_refreshing(SalOp *op); void sal_op_release(SalOp *h); +/*same as release, but does not stop refresher if any*/ +void* sal_op_unref(SalOp* op); + void sal_op_authenticate(SalOp *h, const SalAuthInfo *info); void sal_op_cancel_authentication(SalOp *h); void sal_op_set_user_pointer(SalOp *h, void *up); diff --git a/tester/register_tester.c b/tester/register_tester.c index 0798bd61f..15584fc6f 100644 --- a/tester/register_tester.c +++ b/tester/register_tester.c @@ -119,7 +119,8 @@ static void register_with_refresh_base_3(LinphoneCore* lc linphone_core_add_proxy_config(lc,proxy_cfg); linphone_core_set_default_proxy(lc,proxy_cfg); - while (counters->number_of_LinphoneRegistrationOk<1+(refresh!=0) && retry++ <310) { + while (counters->number_of_LinphoneRegistrationOk<1+(refresh!=0) + && retry++ <(110 /*only wait 11 s if final state is progress*/+(expected_final_state==LinphoneRegistrationProgress?0:200))) { linphone_core_iterate(lc); if (counters->number_of_auth_info_requested>0 && linphone_proxy_config_get_state(proxy_cfg) == LinphoneRegistrationFailed && late_auth_info) { if (!linphone_core_get_auth_info_list(lc)) { @@ -197,6 +198,52 @@ static void simple_register(){ linphone_core_manager_destroy(lcm); } +static void simple_unregister(){ + LinphoneCoreManager* lcm = create_lcm(); + stats* counters = &lcm->stat; + LinphoneProxyConfig* proxy_config; + register_with_refresh_base(lcm->lc,FALSE,NULL,NULL); + + linphone_core_get_default_proxy(lcm->lc,&proxy_config); + + linphone_proxy_config_edit(proxy_config); + reset_counters(counters); /*clear stats*/ + + /*nothing is supposed to arrive until done*/ + CU_ASSERT_FALSE(wait_for_until(lcm->lc,lcm->lc,&counters->number_of_LinphoneRegistrationCleared,1,3000)); + linphone_proxy_config_enable_register(proxy_config,FALSE); + linphone_proxy_config_done(proxy_config); + CU_ASSERT_TRUE(wait_for(lcm->lc,lcm->lc,&counters->number_of_LinphoneRegistrationCleared,1)); + linphone_core_manager_destroy(lcm); +} + +static void change_expires(){ + LinphoneCoreManager* lcm = create_lcm(); + stats* counters = &lcm->stat; + LinphoneProxyConfig* proxy_config; + register_with_refresh_base(lcm->lc,FALSE,NULL,NULL); + + linphone_core_get_default_proxy(lcm->lc,&proxy_config); + + linphone_proxy_config_edit(proxy_config); + reset_counters(counters); /*clear stats*/ + + /*nothing is supposed to arrive until done*/ + CU_ASSERT_FALSE(wait_for_until(lcm->lc,lcm->lc,&counters->number_of_LinphoneRegistrationCleared,1,3000)); + + linphone_proxy_config_set_expires(proxy_config,3); + + linphone_proxy_config_done(proxy_config); + CU_ASSERT_TRUE(wait_for(lcm->lc,lcm->lc,&counters->number_of_LinphoneRegistrationOk,1)); + /*wait 2s without receive refresh*/ + CU_ASSERT_FALSE(wait_for_until(lcm->lc,lcm->lc,&counters->number_of_LinphoneRegistrationOk,2,2000)); + /* now, it should be ok*/ + CU_ASSERT_TRUE(wait_for(lcm->lc,lcm->lc,&counters->number_of_LinphoneRegistrationOk,2)); + + + linphone_core_manager_destroy(lcm); +} + /*take care of min expires configuration from server*/ static void simple_register_with_refresh() { LinphoneCoreManager* lcm = create_lcm(); @@ -467,6 +514,101 @@ static void transport_change(){ linphone_core_manager_destroy(mgr); } +static void proxy_transport_change(){ + LinphoneCoreManager* lcm = create_lcm(); + stats* counters = &lcm->stat; + LinphoneProxyConfig* proxy_config; + LinphoneAddress* addr; + char* addr_as_string; + LinphoneAuthInfo *info=linphone_auth_info_new(test_username,NULL,test_password,NULL,auth_domain,NULL); /*create authentication structure from identity*/ + linphone_core_add_auth_info(lcm->lc,info); /*add authentication info to LinphoneCore*/ + + register_with_refresh_base(lcm->lc,FALSE,auth_domain,NULL); + + linphone_core_get_default_proxy(lcm->lc,&proxy_config); + reset_counters(counters); /*clear stats*/ + linphone_proxy_config_edit(proxy_config); + + CU_ASSERT_FALSE(wait_for_until(lcm->lc,lcm->lc,&counters->number_of_LinphoneRegistrationCleared,1,3000)); + addr = linphone_address_new(linphone_proxy_config_get_addr(proxy_config)); + + if (LinphoneTransportTcp == linphone_address_get_transport(addr)) { + linphone_address_set_transport(addr,LinphoneTransportUdp); + } else { + linphone_address_set_transport(addr,LinphoneTransportTcp); + } + linphone_proxy_config_set_server_addr(proxy_config,addr_as_string=linphone_address_as_string(addr)); + + linphone_proxy_config_done(proxy_config); + + CU_ASSERT(wait_for(lcm->lc,lcm->lc,&counters->number_of_LinphoneRegistrationOk,1)); + /*as we change p[roxy server destination, we should'nt be notified about the clear*/ + CU_ASSERT_EQUAL(counters->number_of_LinphoneRegistrationCleared,0); + ms_free(addr_as_string); + linphone_address_destroy(addr); + linphone_core_manager_destroy(lcm); + +} +static void proxy_transport_change_with_wrong_port() { + LinphoneCoreManager* lcm = create_lcm(); + stats* counters = &lcm->stat; + LinphoneProxyConfig* proxy_config; + LinphoneAuthInfo *info=linphone_auth_info_new(test_username,NULL,test_password,NULL,auth_domain,NULL); /*create authentication structure from identity*/ + char route[256]; + LCSipTransports transport= {LC_SIP_TRANSPORT_RANDOM,LC_SIP_TRANSPORT_RANDOM,LC_SIP_TRANSPORT_RANDOM,LC_SIP_TRANSPORT_RANDOM}; + sprintf(route,"sip:%s",test_route); + + linphone_core_add_auth_info(lcm->lc,info); /*add authentication info to LinphoneCore*/ + + register_with_refresh_base_3(lcm->lc, FALSE, auth_domain, "sip2.linphone.org:5987", 0,transport,LinphoneRegistrationProgress); + + linphone_core_get_default_proxy(lcm->lc,&proxy_config); + linphone_proxy_config_edit(proxy_config); + + CU_ASSERT_FALSE(wait_for_until(lcm->lc,lcm->lc,&counters->number_of_LinphoneRegistrationCleared,1,3000)); + linphone_proxy_config_set_server_addr(proxy_config,route); + linphone_proxy_config_done(proxy_config); + + CU_ASSERT(wait_for(lcm->lc,lcm->lc,&counters->number_of_LinphoneRegistrationOk,1)); + /*as we change proxy server destination, we should'nt be notified about the clear*/ + CU_ASSERT_EQUAL(counters->number_of_LinphoneRegistrationCleared,0); + CU_ASSERT_EQUAL(counters->number_of_LinphoneRegistrationOk,1); + CU_ASSERT_EQUAL(counters->number_of_LinphoneRegistrationProgress,1); + CU_ASSERT_EQUAL(counters->number_of_LinphoneRegistrationFailed,0); + + linphone_core_manager_destroy(lcm); + +} + +static void proxy_transport_change_with_wrong_port_givin_up() { + LinphoneCoreManager* lcm = create_lcm(); + stats* counters = &lcm->stat; + LinphoneProxyConfig* proxy_config; + LinphoneAuthInfo *info=linphone_auth_info_new(test_username,NULL,test_password,NULL,auth_domain,NULL); /*create authentication structure from identity*/ + char route[256]; + LCSipTransports transport= {LC_SIP_TRANSPORT_RANDOM,LC_SIP_TRANSPORT_RANDOM,LC_SIP_TRANSPORT_RANDOM,LC_SIP_TRANSPORT_RANDOM}; + sprintf(route,"sip:%s",test_route); + + linphone_core_add_auth_info(lcm->lc,info); /*add authentication info to LinphoneCore*/ + + register_with_refresh_base_3(lcm->lc, FALSE, auth_domain, "sip2.linphone.org:5987", 0,transport,LinphoneRegistrationProgress); + + linphone_core_get_default_proxy(lcm->lc,&proxy_config); + linphone_proxy_config_edit(proxy_config); + + CU_ASSERT_FALSE(wait_for_until(lcm->lc,lcm->lc,&counters->number_of_LinphoneRegistrationCleared,1,3000)); + linphone_proxy_config_enableregister(proxy_config,FALSE); + linphone_proxy_config_done(proxy_config); + + CU_ASSERT(wait_for(lcm->lc,lcm->lc,&counters->number_of_LinphoneRegistrationCleared,1)); + CU_ASSERT_EQUAL(counters->number_of_LinphoneRegistrationOk,0); + CU_ASSERT_EQUAL(counters->number_of_LinphoneRegistrationProgress,1); + CU_ASSERT_EQUAL(counters->number_of_LinphoneRegistrationFailed,0); + + linphone_core_manager_destroy(lcm); + +} + static void io_recv_error(){ LinphoneCoreManager *mgr; LinphoneCore* lc; @@ -567,22 +709,17 @@ static void io_recv_error_without_active_register(){ for (proxys=ms_list_copy(linphone_core_get_proxy_config_list(lc));proxys!=NULL;proxys=proxys->next) { LinphoneProxyConfig* proxy_cfg=(LinphoneProxyConfig*)proxys->data; linphone_proxy_config_edit(proxy_cfg); + linphone_proxy_config_enableregister(proxy_cfg,FALSE); + linphone_proxy_config_done(proxy_cfg); } ms_list_free(proxys); /*wait for unregistrations*/ CU_ASSERT_TRUE(wait_for(lc,lc,&counters->number_of_LinphoneRegistrationCleared,register_ok /*because 1 udp*/)); - for (proxys=ms_list_copy(linphone_core_get_proxy_config_list(lc));proxys!=NULL;proxys=proxys->next) { - LinphoneProxyConfig* proxy_cfg=(LinphoneProxyConfig*)proxys->data; - linphone_proxy_config_enable_register(proxy_cfg,FALSE); - linphone_proxy_config_done(proxy_cfg); - } - ms_list_free(proxys); - sal_set_recv_error(lc->sal, 0); /*nothing should happen because no active registration*/ - CU_ASSERT_FALSE(wait_for(lc,lc,&counters->number_of_LinphoneRegistrationProgress,2*(register_ok-number_of_udp_proxy) /*because 1 udp*/)); + CU_ASSERT_FALSE(wait_for_until(lc,lc,&counters->number_of_LinphoneRegistrationProgress,2*(register_ok-number_of_udp_proxy) /*because 1 udp*/,3000)); CU_ASSERT_EQUAL(counters->number_of_LinphoneRegistrationFailed,0) @@ -669,6 +806,7 @@ static void tls_wildcard_register(){ test_t register_tests[] = { { "Simple register", simple_register }, + { "Simple register unregister", simple_unregister }, { "TCP register", simple_tcp_register }, { "TCP register compatibility mode", simple_tcp_register_compatibility_mode }, { "TLS register", simple_tls_register }, @@ -688,7 +826,11 @@ test_t register_tests[] = { { "Authenticated register with refresh", simple_auth_register_with_refresh }, { "Register with refresh and send error", register_with_refresh_with_send_error }, { "Multi account", multiple_proxy }, - { "Transport change", transport_change }, + { "Transport changes", transport_change }, + { "Proxy transport changes", proxy_transport_change}, + { "Proxy transport changes with wrong address at first", proxy_transport_change_with_wrong_port}, + { "Proxy transport changes with wrong address, giving up",proxy_transport_change_with_wrong_port_givin_up}, + { "Change expires", change_expires}, { "Network state change", network_state_change }, { "Io recv error", io_recv_error }, { "Io recv error with recovery", io_recv_error_retry_immediatly}, diff --git a/tester/setup_tester.c b/tester/setup_tester.c index dc30e058e..2ef643b86 100644 --- a/tester/setup_tester.c +++ b/tester/setup_tester.c @@ -21,7 +21,7 @@ #include "linphonecore.h" #include "liblinphone_tester.h" #include "lpconfig.h" - +#include "private.h" static void core_init_test(void) { LinphoneCoreVTable v_table; @@ -104,10 +104,70 @@ static void linphone_lpconfig_from_buffer(){ lp_config_destroy(conf); } +void linphone_proxy_config_address_equal_test() { + LinphoneAddress *a = linphone_address_new("sip:toto@titi"); + LinphoneAddress *b = linphone_address_new("sips:toto@titi"); + LinphoneAddress *c = linphone_address_new("sip:toto@titi;transport=tcp"); + LinphoneAddress *d = linphone_address_new("sip:toto@titu"); + LinphoneAddress *e = linphone_address_new("sip:toto@titi;transport=udp"); + CU_ASSERT_FALSE(linphone_proxy_config_address_equal(a,NULL)); + CU_ASSERT_FALSE(linphone_proxy_config_address_equal(a,b)); + CU_ASSERT_FALSE(linphone_proxy_config_address_equal(a,c)); + CU_ASSERT_FALSE(linphone_proxy_config_address_equal(a,d)); + CU_ASSERT_TRUE(linphone_proxy_config_address_equal(a,e)); + CU_ASSERT_TRUE(linphone_proxy_config_address_equal(NULL,NULL)); + + linphone_address_destroy(a); + linphone_address_destroy(b); + linphone_address_destroy(c); + linphone_address_destroy(d); +} + +void linphone_proxy_config_is_server_config_changed_test() { + LinphoneProxyConfig* proxy_config = linphone_proxy_config_new(); + + linphone_proxy_config_set_identity(proxy_config,"sip:toto@titi"); + linphone_proxy_config_edit(proxy_config); + linphone_proxy_config_set_identity(proxy_config,"sips:toto@titi"); + CU_ASSERT_TRUE(linphone_proxy_config_is_server_config_changed(proxy_config)); + + linphone_proxy_config_set_server_addr(proxy_config,"sip:sip.linphone.org"); + linphone_proxy_config_edit(proxy_config); + linphone_proxy_config_set_server_addr(proxy_config,"sip:toto.com"); + CU_ASSERT_TRUE(linphone_proxy_config_is_server_config_changed(proxy_config)); + + linphone_proxy_config_set_route(proxy_config,"sip:sip.linphone.org"); + linphone_proxy_config_edit(proxy_config); + linphone_proxy_config_set_route(proxy_config,"sip:sip.linphone.org:4444"); + CU_ASSERT_TRUE(linphone_proxy_config_is_server_config_changed(proxy_config)); + + linphone_proxy_config_set_route(proxy_config,"sip:sip.linphone.org"); + linphone_proxy_config_edit(proxy_config); + linphone_proxy_config_set_route(proxy_config,"sip:sip.linphone.org;transport=tcp"); + CU_ASSERT_TRUE(linphone_proxy_config_is_server_config_changed(proxy_config)); + + linphone_proxy_config_set_route(proxy_config,"sip:sip.linphone.org"); + linphone_proxy_config_edit(proxy_config); + linphone_proxy_config_set_route(proxy_config,"sip:sip.linphone.org;param=blue"); + CU_ASSERT_FALSE(linphone_proxy_config_is_server_config_changed(proxy_config)); + + + linphone_proxy_config_edit(proxy_config); + linphone_proxy_config_set_contact_parameters(proxy_config,"blabla=blue"); + CU_ASSERT_FALSE(linphone_proxy_config_is_server_config_changed(proxy_config)); + + linphone_proxy_config_edit(proxy_config); + linphone_proxy_config_enable_register(proxy_config,TRUE); + CU_ASSERT_FALSE(linphone_proxy_config_is_server_config_changed(proxy_config)); + + linphone_proxy_config_destroy(proxy_config); +} test_t setup_tests[] = { { "Linphone Address", linphone_address_test }, + { "Linphone proxy config address equal (internal api)", linphone_proxy_config_address_equal_test}, + { "Linphone proxy config server address change (internal api)", linphone_proxy_config_is_server_config_changed_test}, { "Linphone core init/uninit", core_init_test }, { "Linphone random transport port",core_sip_transport_test}, { "Linphone interpret url", linphone_interpret_url_test }, From cc5570cae5726e7b0fab31a651cc8a1dc9e90839 Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Wed, 21 May 2014 10:17:43 +0200 Subject: [PATCH 09/94] proxy route parameter should not be used to check if proxy server address has changed --- coreapi/private.h | 1 - coreapi/proxy.c | 11 ----------- tester/setup_tester.c | 12 ++++++------ 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/coreapi/private.h b/coreapi/private.h index ae1e109bd..08db6a40d 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -434,7 +434,6 @@ struct _LinphoneProxyConfig /*use to check if server config has changed between edit() and done()*/ LinphoneAddress *saved_proxy; LinphoneAddress *saved_identity; - LinphoneAddress *saved_route; /*---*/ }; diff --git a/coreapi/proxy.c b/coreapi/proxy.c index a71ea868a..d7b99aeb2 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -39,12 +39,6 @@ static void linphone_proxy_config_store_server_config(LinphoneProxyConfig* obj) obj->saved_proxy = linphone_address_new(obj->reg_proxy); else obj->saved_proxy = NULL; - - if (obj->saved_route) linphone_address_destroy(obj->saved_route); - if (obj->reg_route) - obj->saved_route = linphone_address_new(obj->reg_route); - else - obj->saved_route = NULL; } bool_t linphone_proxy_config_address_equal(const LinphoneAddress *a, const LinphoneAddress *b) { @@ -67,7 +61,6 @@ bool_t linphone_proxy_config_address_equal(const LinphoneAddress *a, const Linph bool_t linphone_proxy_config_is_server_config_changed(const LinphoneProxyConfig* obj) { LinphoneAddress *current_identity=obj->reg_identity?linphone_address_new(obj->reg_identity):NULL; LinphoneAddress *current_proxy=obj->reg_proxy?linphone_address_new(obj->reg_proxy):NULL; - LinphoneAddress *current_route=obj->reg_route?linphone_address_new(obj->reg_route):NULL; if (!linphone_proxy_config_address_equal(obj->saved_identity,current_identity)) return TRUE; @@ -75,9 +68,6 @@ bool_t linphone_proxy_config_is_server_config_changed(const LinphoneProxyConfig* if (!linphone_proxy_config_address_equal(obj->saved_proxy,current_proxy)) return TRUE; - if (!linphone_proxy_config_address_equal(obj->saved_route,current_route)) - return TRUE; - return FALSE; } @@ -162,7 +152,6 @@ void linphone_proxy_config_destroy(LinphoneProxyConfig *obj){ if (obj->contact_uri_params) ms_free(obj->contact_uri_params); if (obj->saved_proxy!=NULL) linphone_address_destroy(obj->saved_proxy); if (obj->saved_identity!=NULL) ms_free(obj->saved_identity); - if (obj->saved_route!=NULL) ms_free(obj->saved_route); ms_free(obj); } diff --git a/tester/setup_tester.c b/tester/setup_tester.c index 2ef643b86..760cdf6bd 100644 --- a/tester/setup_tester.c +++ b/tester/setup_tester.c @@ -137,19 +137,19 @@ void linphone_proxy_config_is_server_config_changed_test() { linphone_proxy_config_set_server_addr(proxy_config,"sip:toto.com"); CU_ASSERT_TRUE(linphone_proxy_config_is_server_config_changed(proxy_config)); - linphone_proxy_config_set_route(proxy_config,"sip:sip.linphone.org"); + linphone_proxy_config_set_server_addr(proxy_config,"sip:sip.linphone.org"); linphone_proxy_config_edit(proxy_config); - linphone_proxy_config_set_route(proxy_config,"sip:sip.linphone.org:4444"); + linphone_proxy_config_set_server_addr(proxy_config,"sip:sip.linphone.org:4444"); CU_ASSERT_TRUE(linphone_proxy_config_is_server_config_changed(proxy_config)); - linphone_proxy_config_set_route(proxy_config,"sip:sip.linphone.org"); + linphone_proxy_config_set_server_addr(proxy_config,"sip:sip.linphone.org"); linphone_proxy_config_edit(proxy_config); - linphone_proxy_config_set_route(proxy_config,"sip:sip.linphone.org;transport=tcp"); + linphone_proxy_config_set_server_addr(proxy_config,"sip:sip.linphone.org;transport=tcp"); CU_ASSERT_TRUE(linphone_proxy_config_is_server_config_changed(proxy_config)); - linphone_proxy_config_set_route(proxy_config,"sip:sip.linphone.org"); + linphone_proxy_config_set_server_addr(proxy_config,"sip:sip.linphone.org"); linphone_proxy_config_edit(proxy_config); - linphone_proxy_config_set_route(proxy_config,"sip:sip.linphone.org;param=blue"); + linphone_proxy_config_set_server_addr(proxy_config,"sip:sip.linphone.org;param=blue"); CU_ASSERT_FALSE(linphone_proxy_config_is_server_config_changed(proxy_config)); From fbc8f77e3ab217adc9826afec8b7b50ed2db0884 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Wed, 21 May 2014 13:10:34 +0200 Subject: [PATCH 10/94] allow crypto lines to be configured from linphonerc, and improve code handling SRTP crypto lines --- coreapi/bellesip_sal/sal_impl.c | 1 + coreapi/bellesip_sal/sal_sdp.c | 82 ++++++++++++--------------------- coreapi/linphonecall.c | 41 +++++++++++++---- coreapi/linphonecore.c | 1 + coreapi/misc.c | 49 ++++++++++++++++++++ coreapi/offeranswer.c | 5 +- coreapi/private.h | 2 + include/sal/sal.h | 2 +- mediastreamer2 | 2 +- oRTP | 2 +- 10 files changed, 118 insertions(+), 69 deletions(-) diff --git a/coreapi/bellesip_sal/sal_impl.c b/coreapi/bellesip_sal/sal_impl.c index a35d85822..0212c8a39 100644 --- a/coreapi/bellesip_sal/sal_impl.c +++ b/coreapi/bellesip_sal/sal_impl.c @@ -999,3 +999,4 @@ void sal_cancel_timer(Sal *sal, belle_sip_source_t *timer) { belle_sip_main_loop_t *ml = belle_sip_stack_get_main_loop(sal->stack); belle_sip_main_loop_remove_source(ml, timer); } + diff --git a/coreapi/bellesip_sal/sal_sdp.c b/coreapi/bellesip_sal/sal_sdp.c index 50e3504a5..23d0d8be4 100644 --- a/coreapi/bellesip_sal/sal_sdp.c +++ b/coreapi/bellesip_sal/sal_sdp.c @@ -146,36 +146,15 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session if ( stream->proto == SalProtoRtpSavp ) { /* add crypto lines */ for ( j=0; jcrypto[j].algo ) { - case MS_AES_128_SHA1_80: - enc_name="AES_CM_128_HMAC_SHA1_80"; - break; - case MS_AES_128_SHA1_32: - enc_name="AES_CM_128_HMAC_SHA1_32"; - break; - case MS_AES_256_SHA1_32: - enc_name="AES_CM_256_HMAC_SHA1_32"; - break; - case MS_AES_256_SHA1_80: - enc_name="AES_CM_256_HMAC_SHA1_32"; - break; - case MS_AES_128_NO_AUTH: - ms_warning ( "Unsupported crypto suite: AES_128_NO_AUTH" ); - break; - case MS_NO_CIPHER_SHA1_80: - ms_warning ( "Unsupported crypto suite: NO_CIPHER_SHA1_80" ); - break; - default: - j = SAL_CRYPTO_ALGO_MAX; - /* no break */ - } - if (enc_name){ - snprintf ( buffer, sizeof ( buffer )-1, "%d %s inline:%s", - stream->crypto[j].tag, enc_name, stream->crypto[j].master_key ); - belle_sdp_media_description_add_attribute ( media_desc,belle_sdp_attribute_create ( "crypto",buffer ) ); - } + MSCryptoSuiteNameParams desc; + if (ms_crypto_suite_to_name_params(stream->crypto[j].algo,&desc)==0){ + if (desc.params) + snprintf ( buffer, sizeof ( buffer )-1, "%d %s inline:%s %s", stream->crypto[j].tag, desc.name, stream->crypto[j].master_key,desc.params); + else + snprintf ( buffer, sizeof ( buffer )-1, "%d %s inline:%s", stream->crypto[j].tag, desc.name, stream->crypto[j].master_key ); + + belle_sdp_media_description_add_attribute( media_desc,belle_sdp_attribute_create ("crypto", buffer)); + }else break; } } switch ( stream->dir ) { @@ -330,7 +309,7 @@ static void sdp_parse_payload_types(belle_sdp_media_description_t *media_desc, S static void sdp_parse_media_crypto_parameters(belle_sdp_media_description_t *media_desc, SalStreamDescription *stream) { belle_sip_list_t *attribute_it; belle_sdp_attribute_t *attribute; - char tmp[256], tmp2[256]; + char tmp[256], tmp2[256], parameters[256]={0}; int valid_count = 0; int nb; @@ -341,42 +320,39 @@ static void sdp_parse_media_crypto_parameters(belle_sdp_media_description_t *med attribute=BELLE_SDP_ATTRIBUTE ( attribute_it->data ); if ( keywordcmp ( "crypto",belle_sdp_attribute_get_name ( attribute ) ) ==0 && belle_sdp_attribute_get_value ( attribute ) !=NULL ) { - nb = sscanf ( belle_sdp_attribute_get_value ( attribute ), "%d %256s inline:%256s", + nb = sscanf ( belle_sdp_attribute_get_value ( attribute ), "%d %256s inline:%256s %256s", &stream->crypto[valid_count].tag, tmp, - tmp2 ); - ms_message ( "Found valid crypto line (tag:%d algo:'%s' key:'%s'", - stream->crypto[valid_count].tag, - tmp, - tmp2 ); - if ( nb == 3 ) { - if ( keywordcmp ( "AES_CM_128_HMAC_SHA1_80",tmp ) == 0 ){ - stream->crypto[valid_count].algo = MS_AES_128_SHA1_80; - }else if ( keywordcmp ( "AES_CM_128_HMAC_SHA1_32",tmp ) == 0 ){ - stream->crypto[valid_count].algo = MS_AES_128_SHA1_32; - }else if ( keywordcmp ( "AES_CM_256_HMAC_SHA1_32",tmp ) == 0 ){ - stream->crypto[valid_count].algo = MS_AES_256_SHA1_32; - }else if ( keywordcmp ( "AES_CM_256_HMAC_SHA1_80",tmp ) == 0 ){ - stream->crypto[valid_count].algo = MS_AES_256_SHA1_80; - }else { + tmp2, parameters ); + + if ( nb >= 3 ) { + MSCryptoSuite cs; + MSCryptoSuiteNameParams np; + + np.name=tmp; + np.params=parameters[0]!='\0' ? parameters : NULL; + cs=ms_crypto_suite_build_from_name_params(&np); + if (cs==MS_CRYPTO_SUITE_INVALID){ ms_warning ( "Failed to parse crypto-algo: '%s'", tmp ); stream->crypto[valid_count].algo = 0; - } - if ( stream->crypto[valid_count].algo ) { - strncpy ( stream->crypto[valid_count].master_key, tmp2, 41 ); - stream->crypto[valid_count].master_key[40] = '\0'; + }else{ + char *sep; + strncpy ( stream->crypto[valid_count].master_key, tmp2, sizeof(stream->crypto[valid_count].master_key)-1 ); + sep=strchr(stream->crypto[valid_count].master_key,'|'); + if (sep) *sep='\0'; + stream->crypto[valid_count].algo = cs; ms_message ( "Found valid crypto line (tag:%d algo:'%s' key:'%s'", stream->crypto[valid_count].tag, tmp, stream->crypto[valid_count].master_key ); valid_count++; } - } else { + }else{ ms_warning ( "sdp has a strange a= line (%s) nb=%i",belle_sdp_attribute_get_value ( attribute ),nb ); } } } - ms_message ( "Found: %d valid crypto lines", valid_count ); + ms_message("Found: %d valid crypto lines", valid_count ); } static void sdp_parse_media_ice_parameters(belle_sdp_media_description_t *media_desc, SalStreamDescription *stream) { diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 27ce04a3a..b91bf71c2 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -232,9 +232,35 @@ static void update_media_description_from_stun(SalMediaDescription *md, const St } } +static int setup_encryption_key(SalSrtpCryptoAlgo *crypto, MSCryptoSuite suite, unsigned int tag){ + int keylen=0; + crypto->tag=tag; + crypto->algo=suite; + switch(suite){ + case MS_AES_128_SHA1_80: + case MS_AES_128_SHA1_32: + case MS_AES_128_NO_AUTH: + case MS_NO_CIPHER_SHA1_80: /*not sure for this one*/ + keylen=30; + break; + case MS_AES_256_SHA1_80: + case MS_AES_256_SHA1_32: + keylen=46; + break; + case MS_CRYPTO_SUITE_INVALID: + break; + } + if (keylen==0 || !generate_b64_crypto_key(30, crypto->master_key, SAL_SRTP_KEY_SIZE)){ + ms_error("Could not generate SRTP key."); + crypto->algo = 0; + return -1; + } + return 0; +} + static void setup_encryption_keys(LinphoneCall *call, SalMediaDescription *md){ LinphoneCore *lc=call->core; - int i; + int i,j; SalMediaDescription *old_md=call->localdesc; bool_t keep_srtp_keys=lp_config_get_int(lc->config,"sip","keep_srtp_keys",1); @@ -247,15 +273,10 @@ static void setup_encryption_keys(LinphoneCall *call, SalMediaDescription *md){ memcpy(&md->streams[i].crypto[j],&old_md->streams[i].crypto[j],sizeof(SalSrtpCryptoAlgo)); } }else{ - md->streams[i].crypto[0].tag = 1; - md->streams[i].crypto[0].algo = MS_AES_128_SHA1_80; - if (!generate_b64_crypto_key(30, md->streams[i].crypto[0].master_key, SAL_SRTP_KEY_SIZE)) - md->streams[i].crypto[0].algo = 0; - md->streams[i].crypto[1].tag = 2; - md->streams[i].crypto[1].algo = MS_AES_128_SHA1_32; - if (!generate_b64_crypto_key(30, md->streams[i].crypto[1].master_key, SAL_SRTP_KEY_SIZE)) - md->streams[i].crypto[1].algo = 0; - md->streams[i].crypto[2].algo = 0; + const MSCryptoSuite *suites=linphone_core_get_srtp_crypto_suites(lc); + for(j=0;suites!=NULL && suites[j]!=MS_CRYPTO_SUITE_INVALID && jstreams[i].crypto[j],suites[j],j+1); + } } } } diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 157ce6acf..a9e472388 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -5738,6 +5738,7 @@ void rtp_config_uninit(LinphoneCore *lc) lp_config_set_int(lc->config,"rtp","nortp_timeout",config->nortp_timeout); lp_config_set_int(lc->config,"rtp","audio_adaptive_jitt_comp_enabled",config->audio_adaptive_jitt_comp_enabled); lp_config_set_int(lc->config,"rtp","video_adaptive_jitt_comp_enabled",config->video_adaptive_jitt_comp_enabled); + ms_free(config->srtp_suites); } static void sound_config_uninit(LinphoneCore *lc) diff --git a/coreapi/misc.c b/coreapi/misc.c index 56383fa58..c27147107 100644 --- a/coreapi/misc.c +++ b/coreapi/misc.c @@ -1471,3 +1471,52 @@ void linphone_core_set_tone(LinphoneCore *lc, LinphoneToneID id, const char *aud _linphone_core_set_tone(lc, LinphoneReasonNone, id, audiofile); } +const MSCryptoSuite * linphone_core_get_srtp_crypto_suites(LinphoneCore *lc){ + const char *config=lp_config_get_string(lc->config,"sip","srtp_crypto_suites","AES_CM_128_HMAC_SHA1_80, AES_CM_128_HMAC_SHA1_32"); + char *tmp=ms_strdup(config); + char *sep; + char *pos; + char *nextpos; + char *params; + int found=0; + MSCryptoSuite *result=NULL; + pos=tmp; + do{ + sep=strchr(pos,','); + if (!sep) { + sep=pos+strlen(pos); + nextpos=NULL; + }else { + *sep='\0'; + nextpos=sep+1; + } + while(*pos==' ') ++pos; /*strip leading spaces*/ + params=strchr(pos,' '); /*look for params that arrive after crypto suite name*/ + if (params){ + while(*params==' ') ++params; /*strip parameters leading space*/ + } + if (sep-pos>0){ + MSCryptoSuiteNameParams np; + MSCryptoSuite suite; + np.name=pos; + np.params=params; + suite=ms_crypto_suite_build_from_name_params(&np); + if (suite!=MS_CRYPTO_SUITE_INVALID){ + result=ms_realloc(result,found+1+1); + result[found]=suite; + result[found+1]=MS_CRYPTO_SUITE_INVALID; + found++; + ms_message("Configured srtp crypto suite: %s %s",np.name,np.params ? np.params : ""); + } + } + pos=nextpos; + }while(pos); + ms_free(tmp); + if (lc->rtp_conf.srtp_suites){ + ms_free(lc->rtp_conf.srtp_suites); + lc->rtp_conf.srtp_suites=NULL; + } + lc->rtp_conf.srtp_suites=result; + return result; +} + diff --git a/coreapi/offeranswer.c b/coreapi/offeranswer.c index db2a28b76..fd99f421c 100644 --- a/coreapi/offeranswer.c +++ b/coreapi/offeranswer.c @@ -156,17 +156,16 @@ static bool_t match_crypto_algo(const SalSrtpCryptoAlgo* local, const SalSrtpCry result->algo = remote[i].algo; /* We're answering an SDP offer. Supply our master key, associated with the remote supplied tag */ if (use_local_key) { - strncpy(result->master_key, local[j].master_key, 41); + strncpy(result->master_key, local[j].master_key, sizeof(result->master_key) ); result->tag = remote[i].tag; *choosen_local_tag = local[j].tag; } /* We received an answer to our SDP crypto proposal. Copy matching algo remote master key to result, and memorize local tag */ else { - strncpy(result->master_key, remote[i].master_key, 41); + strncpy(result->master_key, remote[i].master_key, sizeof(result->master_key)); result->tag = local[j].tag; *choosen_local_tag = local[j].tag; } - result->master_key[40] = '\0'; return TRUE; } } diff --git a/coreapi/private.h b/coreapi/private.h index 08db6a40d..d43f6454f 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -519,6 +519,7 @@ typedef struct rtp_config int video_jitt_comp; /*jitter compensation*/ int nortp_timeout; int disable_upnp; + MSCryptoSuite *srtp_suites; bool_t rtp_no_xmit_on_audio_mute; /* stop rtp xmit when audio muted */ bool_t audio_adaptive_jitt_comp_enabled; @@ -886,6 +887,7 @@ static inline const LinphoneErrorInfo *linphone_error_info_from_sal_op(const Sal return (const LinphoneErrorInfo*)sal_op_get_error_info(op); } +const MSCryptoSuite * linphone_core_get_srtp_crypto_suites(LinphoneCore *lc); /** Belle Sip-based objects need unique ids */ diff --git a/include/sal/sal.h b/include/sal/sal.h index 55c5ebf20..fbeaf2e14 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -166,7 +166,7 @@ typedef struct SalIceRemoteCandidate { #define SAL_MEDIA_DESCRIPTION_MAX_ICE_PWD_LEN 256 /*sufficient for 256bit keys encoded in base 64*/ -#define SAL_SRTP_KEY_SIZE 64 +#define SAL_SRTP_KEY_SIZE 128 typedef struct SalSrtpCryptoAlgo { unsigned int tag; diff --git a/mediastreamer2 b/mediastreamer2 index ad376cf21..b6ad773d4 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit ad376cf215143507c1f8e297084e210939e7f31b +Subproject commit b6ad773d4896b36fd707b6fc4f743b6b7df7325a diff --git a/oRTP b/oRTP index 73317c9bf..e4f28d143 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 73317c9bf59700b44f85008f6e254476bbe756eb +Subproject commit e4f28d1434b8f18061fb6a45a80649c4bc13cf9a From da54b475d78f0fbb56dd4cba94fc2a9782613362 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Wed, 21 May 2014 15:43:58 +0200 Subject: [PATCH 11/94] improve management of comments in linphonerc parser --- coreapi/lpconfig.c | 102 ++++++++++++++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 34 deletions(-) diff --git a/coreapi/lpconfig.c b/coreapi/lpconfig.c index 0ac692cde..9dcce9f4d 100644 --- a/coreapi/lpconfig.c +++ b/coreapi/lpconfig.c @@ -49,6 +49,7 @@ typedef struct _LpItem{ char *key; char *value; + int is_comment; } LpItem; typedef struct _LpSectionParam{ @@ -78,6 +79,13 @@ LpItem * lp_item_new(const char *key, const char *value){ return item; } +LpItem * lp_comment_new(const char *comment){ + LpItem *item=lp_new0(LpItem,1); + item->value=ortp_strdup(comment); + item->is_comment=TRUE; + return item; +} + LpSectionParam *lp_section_param_new(const char *key, const char *value){ LpSectionParam *param = lp_new0(LpSectionParam, 1); param->key = ortp_strdup(key); @@ -93,7 +101,7 @@ LpSection *lp_section_new(const char *name){ void lp_item_destroy(void *pitem){ LpItem *item=(LpItem*)pitem; - ortp_free(item->key); + if (item->key) ortp_free(item->key); ortp_free(item->value); free(item); } @@ -138,6 +146,14 @@ static bool_t is_first_char(const char *start, const char *pos){ return TRUE; } +static int is_a_comment(const char *str){ + while (*str==' '){ + str++; + } + if (*str=='#') return 1; + return 0; +} + LpSection *lp_config_find_section(const LpConfig *lpconfig, const char *name){ LpSection *sec; MSList *elem; @@ -170,7 +186,7 @@ LpItem *lp_section_find_item(const LpSection *sec, const char *name){ /*printf("Looking for item %s\n",name);*/ for (elem=sec->items;elem!=NULL;elem=ms_list_next(elem)){ item=(LpItem*)elem->data; - if (strcmp(item->key,name)==0) { + if (!item->is_comment && strcmp(item->key,name)==0) { /*printf("Item %s found\n",name);*/ return item; } @@ -182,9 +198,10 @@ static LpSection* lp_config_parse_line(LpConfig* lpconfig, const char* line, LpS LpSectionParam *params = NULL; char *pos1,*pos2; int nbs; - static char secname[MAX_LEN]; - static char key[MAX_LEN]; - static char value[MAX_LEN]; + int size=strlen(line)+1; + char *secname=ms_malloc(size); + char *key=ms_malloc(size); + char *value=ms_malloc(size); LpItem *item; pos1=strchr(line,'['); @@ -230,43 +247,53 @@ static LpSection* lp_config_parse_line(LpConfig* lpconfig, const char* line, LpS } } }else { - pos1=strchr(line,'='); - if (pos1!=NULL){ - key[0]='\0'; + if (is_a_comment(line)){ + if (cur){ + LpItem *comment=lp_comment_new(line); + lp_section_add_item(cur,comment); + } + }else{ + pos1=strchr(line,'='); + if (pos1!=NULL){ + key[0]='\0'; - *pos1='\0'; - if (sscanf(line,"%s",key)>0){ + *pos1='\0'; + if (sscanf(line,"%s",key)>0){ - pos1++; - pos2=strchr(pos1,'\r'); - if (pos2==NULL) - pos2=strchr(pos1,'\n'); - if (pos2==NULL) pos2=pos1+strlen(pos1); - else { - *pos2='\0'; /*replace the '\n' */ - } - /* remove ending white spaces */ - for (; pos2>pos1 && pos2[-1]==' ';pos2--) pos2[-1]='\0'; + pos1++; + pos2=strchr(pos1,'\r'); + if (pos2==NULL) + pos2=strchr(pos1,'\n'); + if (pos2==NULL) pos2=pos1+strlen(pos1); + else { + *pos2='\0'; /*replace the '\n' */ + } + /* remove ending white spaces */ + for (; pos2>pos1 && pos2[-1]==' ';pos2--) pos2[-1]='\0'; - if (pos2-pos1>=0){ - /* found a pair key,value */ + if (pos2-pos1>=0){ + /* found a pair key,value */ - if (cur!=NULL){ - item=lp_section_find_item(cur,key); - if (item==NULL){ - lp_section_add_item(cur,lp_item_new(key,pos1)); + if (cur!=NULL){ + item=lp_section_find_item(cur,key); + if (item==NULL){ + lp_section_add_item(cur,lp_item_new(key,pos1)); + }else{ + ortp_free(item->value); + item->value=ortp_strdup(pos1); + } + /*ms_message("Found %s=%s",key,pos1);*/ }else{ - ortp_free(item->value); - item->value=ortp_strdup(pos1); + ms_warning("found key,item but no sections"); } - /*ms_message("Found %s=%s",key,pos1);*/ - }else{ - ms_warning("found key,item but no sections"); } } } } } + ms_free(key); + ms_free(value); + ms_free(secname); return cur; } @@ -425,13 +452,16 @@ bool_t lp_config_get_range(const LpConfig *lpconfig, const char *section, const } } + int lp_config_get_int(const LpConfig *lpconfig,const char *section, const char *key, int default_value){ const char *str=lp_config_get_string(lpconfig,section,key,NULL); if (str!=NULL) { int ret=0; + if (strstr(str,"0x")==str){ sscanf(str,"%x",&ret); - }else ret=atoi(str); + }else + sscanf(str,"%i",&ret); return ret; } else return default_value; @@ -510,7 +540,10 @@ void lp_config_set_float(LpConfig *lpconfig,const char *section, const char *key } void lp_item_write(LpItem *item, FILE *file){ - fprintf(file,"%s=%s\n",item->key,item->value); + if (item->is_comment) + fprintf(file,"%s",item->value); + else + fprintf(file,"%s=%s\n",item->key,item->value); } void lp_section_param_write(LpSectionParam *param, FILE *file){ @@ -566,7 +599,8 @@ void lp_config_for_each_entry(const LpConfig *lpconfig, const char *section, voi if (sec!=NULL){ for (elem=sec->items;elem!=NULL;elem=ms_list_next(elem)){ item=(LpItem*)elem->data; - callback(item->key, ctx); + if (!item->is_comment) + callback(item->key, ctx); } } } From e93ac6bf319e761563f8c8864b1c3269b4ef4430 Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Wed, 21 May 2014 15:59:25 +0200 Subject: [PATCH 12/94] add test for Simple conference with ICE --- tester/call_tester.c | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/tester/call_tester.c b/tester/call_tester.c index d20cf1fd6..60c9b8d6b 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -1221,10 +1221,8 @@ static void call_waiting_indication_with_privacy(void) { call_waiting_indication_with_param(TRUE); } -static void simple_conference(void) { - LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); - LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); - LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc"); +static void simple_conference_base(LinphoneCoreManager* marie, LinphoneCoreManager* pauline, LinphoneCoreManager* laure) { + stats initial_marie_stat; stats initial_pauline_stat; stats initial_laure_stat; @@ -1266,6 +1264,16 @@ static void simple_conference(void) { CU_ASSERT_TRUE(linphone_core_is_in_conference(marie->lc)); CU_ASSERT_EQUAL(linphone_core_get_conference_size(marie->lc),3) + if (linphone_core_get_firewall_policy(marie->lc) == LinphonePolicyUseIce) { + if (linphone_core_get_firewall_policy(pauline->lc) == LinphonePolicyUseIce) { + check_ice(marie,pauline,LinphoneIceStateHostConnection); + } + if (linphone_core_get_firewall_policy(laure->lc) == LinphonePolicyUseIce) { + check_ice(marie,laure,LinphoneIceStateHostConnection); + } + } + + linphone_core_terminate_conference(marie->lc); CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallEnd,1,2000)); @@ -1273,10 +1281,35 @@ static void simple_conference(void) { CU_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallEnd,1,2000)); + + ms_list_free(lcs); +} +static void simple_conference(void) { + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc"); + simple_conference_base(marie,pauline,laure); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); + linphone_core_manager_destroy(laure); +} + +static void simple_conference_with_ice(void) { + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + LinphoneCoreManager* laure = linphone_core_manager_new( "laure_rc"); + + linphone_core_set_firewall_policy(marie->lc,LinphonePolicyUseIce); + linphone_core_set_stun_server(marie->lc,"stun.linphone.org"); + linphone_core_set_firewall_policy(pauline->lc,LinphonePolicyUseIce); + linphone_core_set_stun_server(pauline->lc,"stun.linphone.org"); + linphone_core_set_firewall_policy(laure->lc,LinphonePolicyUseIce); + linphone_core_set_stun_server(laure->lc,"stun.linphone.org"); + + simple_conference_base(marie,pauline,laure); linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); linphone_core_manager_destroy(laure); - ms_list_free(lcs); } static void srtp_call() { @@ -2173,6 +2206,7 @@ test_t call_tests[] = { { "Call waiting indication", call_waiting_indication }, { "Call waiting indication with privacy", call_waiting_indication_with_privacy }, { "Simple conference", simple_conference }, + { "Simple conference with ICE",simple_conference_with_ice}, { "Simple call transfer", simple_call_transfer }, { "Unattended call transfer", unattended_call_transfer }, { "Unattended call transfer with error", unattended_call_transfer_with_error }, From 78e59e5bec2fb8c44811118e4181776a8c9cf566 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Wed, 21 May 2014 16:03:52 +0200 Subject: [PATCH 13/94] suppress warnings --- coreapi/message_storage.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index 7b15c15a0..5a66393f4 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -321,7 +321,7 @@ static int migrate_messages(void* data,int argc, char** argv, char** column_name sqlite3_free(buf); } } else { - ms_warning("Cannot parse time %s from id %s\n", argv[1], argv[0]); + ms_warning("Cannot parse time %s from id %s", argv[1], argv[0]); } return 0; } @@ -335,7 +335,7 @@ static void linphone_migrate_timestamps(sqlite3* db){ ms_warning("Error migrating outgoing messages: %s.\n", errmsg); sqlite3_free(errmsg); } else { - ms_message("Migrated message timestamps to UTC\n"); + ms_message("Migrated message timestamps to UTC"); } } @@ -346,7 +346,7 @@ void linphone_update_table(sqlite3* db) { // for image url storage ret=sqlite3_exec(db,"ALTER TABLE history ADD COLUMN url TEXT;",NULL,NULL,&errmsg); if(ret != SQLITE_OK) { - ms_warning("Table already up to date: %s.\n", errmsg); + ms_message("Table already up to date: %s.", errmsg); sqlite3_free(errmsg); } else { ms_debug("Table updated successfully for URL."); @@ -355,14 +355,13 @@ void linphone_update_table(sqlite3* db) { // for UTC timestamp storage ret = sqlite3_exec(db, "ALTER TABLE history ADD COLUMN utc INTEGER;", NULL,NULL,&errmsg); if( ret != SQLITE_OK ){ - ms_warning("Table already up to date: %s.\n", errmsg); + ms_message("Table already up to date: %s.", errmsg); sqlite3_free(errmsg); } else { ms_debug("Table updated successfully for UTC."); + // migrate from old text-based timestamps to unix time-based timestamps + linphone_migrate_timestamps(db); } - - // migrate from old text-based timestamps to unix time-based timestamps - linphone_migrate_timestamps(db); } void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) { From 5f9076251368c733bcd7befcc0bbd4011c2e4d42 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Wed, 21 May 2014 16:25:32 +0200 Subject: [PATCH 14/94] fix crash with conference and ICE --- coreapi/conference.c | 3 ++- tester/call_tester.c | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/coreapi/conference.c b/coreapi/conference.c index 9e6dc7730..7bee313dd 100644 --- a/coreapi/conference.c +++ b/coreapi/conference.c @@ -200,7 +200,8 @@ int linphone_core_add_to_conference(LinphoneCore *lc, LinphoneCall *call){ params->has_video=FALSE; if (call->audiostream || call->videostream){ - linphone_call_stop_media_streams (call); /*free the audio & video local resources*/ + linphone_call_stop_media_streams(call); /*free the audio & video local resources*/ + linphone_call_init_media_streams(call); } if (call==lc->current_call){ lc->current_call=NULL; diff --git a/tester/call_tester.c b/tester/call_tester.c index 60c9b8d6b..852bc4d68 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -1264,6 +1264,8 @@ static void simple_conference_base(LinphoneCoreManager* marie, LinphoneCoreManag CU_ASSERT_TRUE(linphone_core_is_in_conference(marie->lc)); CU_ASSERT_EQUAL(linphone_core_get_conference_size(marie->lc),3) + /* + * FIXME: check_ice cannot work as it is today because there is no current call for the party that hosts the conference if (linphone_core_get_firewall_policy(marie->lc) == LinphonePolicyUseIce) { if (linphone_core_get_firewall_policy(pauline->lc) == LinphonePolicyUseIce) { check_ice(marie,pauline,LinphoneIceStateHostConnection); @@ -1272,7 +1274,7 @@ static void simple_conference_base(LinphoneCoreManager* marie, LinphoneCoreManag check_ice(marie,laure,LinphoneIceStateHostConnection); } } - + */ linphone_core_terminate_conference(marie->lc); From d9a4b6e15e97dc59ec13fa24840111b4d909a522 Mon Sep 17 00:00:00 2001 From: Pierre-Eric Pelloux-Prayer Date: Wed, 21 May 2014 16:27:24 +0200 Subject: [PATCH 15/94] propertybox: only expose simple display filter in video out setting Do not expose 'decode and display' type filter. --- gtk/propertybox.c | 55 ++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/gtk/propertybox.c b/gtk/propertybox.c index 598e2f384..81d3db345 100644 --- a/gtk/propertybox.c +++ b/gtk/propertybox.c @@ -25,8 +25,8 @@ void linphone_gtk_fill_combo_box(GtkWidget *combo, const char **devices, const c const char **p=devices; int i=0,active=-1; GtkTreeModel *model; - - + + if ((model=gtk_combo_box_get_model(GTK_COMBO_BOX(combo)))==NULL){ /*case where combo box is created with no model*/ GtkCellRenderer *renderer=gtk_cell_renderer_text_new(); @@ -40,7 +40,7 @@ void linphone_gtk_fill_combo_box(GtkWidget *combo, const char **devices, const c unless we fill it with a dummy text. This dummy text needs to be removed first*/ } - + for(;*p!=NULL;++p){ if ( cap==CAP_IGNORE || (cap==CAP_CAPTURE && linphone_core_sound_device_can_capture(linphone_gtk_get_core(),*p)) @@ -836,9 +836,9 @@ static void linphone_gtk_proxy_closed(GtkWidget *w){ static void fill_transport_combo_box(GtkWidget *combo, LinphoneTransportType choice, gboolean is_sensitive){ GtkTreeModel *model; GtkTreeIter iter; - + if (GPOINTER_TO_INT(g_object_get_data(G_OBJECT(combo),"combo-updating"))) return; - + if ((model=gtk_combo_box_get_model(GTK_COMBO_BOX(combo)))==NULL){ /*case where combo box is created with no model*/ GtkCellRenderer *renderer=gtk_cell_renderer_text_new(); @@ -882,9 +882,9 @@ void linphone_gtk_proxy_transport_changed(GtkWidget *combo){ const char *addr=gtk_entry_get_text(GTK_ENTRY(proxy)); LinphoneAddress *laddr; LinphoneTransportType new_transport=(LinphoneTransportType)index; - + if (index==-1) return; - + g_object_set_data(G_OBJECT(w),"combo-updating",GINT_TO_POINTER(1)); laddr=linphone_address_new(addr); if (laddr){ @@ -941,7 +941,7 @@ void linphone_gtk_proxy_ok(GtkButton *button){ int index=gtk_combo_box_get_active(GTK_COMBO_BOX(linphone_gtk_get_widget(w,"transport"))); LinphoneTransportType tport=(LinphoneTransportType)index; gboolean was_editing=TRUE; - + if (!cfg){ was_editing=FALSE; cfg=linphone_proxy_config_new(); @@ -978,7 +978,7 @@ void linphone_gtk_proxy_ok(GtkButton *button){ linphone_proxy_config_enable_register(cfg, gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(linphone_gtk_get_widget(w,"register")))); - + /* check if tls was asked but is not enabled in transport configuration*/ if (tport==LinphoneTransportTls){ LCSipTransports tports; @@ -988,7 +988,7 @@ void linphone_gtk_proxy_ok(GtkButton *button){ } linphone_core_set_sip_transports(lc,&tports); } - + if (was_editing){ if (linphone_proxy_config_done(cfg)==-1) return; @@ -1203,15 +1203,15 @@ static void linphone_gtk_show_media_encryption(GtkWidget *pb){ GtkListStore *store; GtkTreeIter iter; GtkCellRenderer *renderer=gtk_cell_renderer_text_new(); - + model=GTK_TREE_MODEL((store=gtk_list_store_new(1,G_TYPE_STRING))); gtk_combo_box_set_model(GTK_COMBO_BOX(combo),model); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo),renderer,TRUE); gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo),renderer,"text",0,NULL); - + gtk_list_store_append(store,&iter); gtk_list_store_set(store,&iter,0,_("None"),-1); - + if (linphone_core_media_encryption_supported(lc,LinphoneMediaEncryptionSRTP)){ gtk_list_store_append(store,&iter); gtk_list_store_set(store,&iter,0,_("SRTP"),-1); @@ -1293,19 +1293,26 @@ void linphone_gtk_fill_video_renderers(GtkWidget *pb){ GtkTreeModel *model=GTK_TREE_MODEL(store=gtk_list_store_new(2,G_TYPE_STRING,G_TYPE_STRING)); if (current_renderer==NULL) current_renderer=video_stream_get_default_video_renderer(); - + gtk_combo_box_set_model(GTK_COMBO_BOX(combo),model); gtk_cell_layout_clear(GTK_CELL_LAYOUT(combo)); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo),renderer,TRUE); gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo),renderer,"text",1,NULL); - - for(i=0,elem=l;elem!=NULL && i<4 ;elem=elem->next,++i){ + + for(i=0,elem=l;elem!=NULL && i<4 ;elem=elem->next){ MSFilterDesc *desc=(MSFilterDesc *)elem->data; GtkTreeIter iter; + + /* do not offer the user to select combo 'decoding/rendering' filter */ + if (desc->enc_fmt != NULL) + continue; + gtk_list_store_append(store,&iter); gtk_list_store_set(store,&iter,0,desc->name,1,desc->text,-1); if (current_renderer && strcmp(current_renderer,desc->name)==0) active=i; + + i++; } ms_list_free(l); if (active!=-1) gtk_combo_box_set_active(GTK_COMBO_BOX(combo),active); @@ -1386,7 +1393,7 @@ void linphone_gtk_show_parameters(void){ tr.udp_port); gtk_spin_button_set_value(GTK_SPIN_BUTTON(linphone_gtk_get_widget(pb,"sip_tcp_port")), tr.tcp_port); - + linphone_core_get_audio_port_range(lc, &min_port, &max_port); gtk_spin_button_set_value(GTK_SPIN_BUTTON(linphone_gtk_get_widget(pb, "audio_min_rtp_port")), min_port); @@ -1404,7 +1411,7 @@ void linphone_gtk_show_parameters(void){ } linphone_gtk_show_media_encryption(pb); - + tmp=linphone_core_get_nat_address(lc); if (tmp) gtk_entry_set_text(GTK_ENTRY(linphone_gtk_get_widget(pb,"nat_address")),tmp); tmp=linphone_core_get_stun_server(lc); @@ -1548,9 +1555,9 @@ void linphone_gtk_edit_tunnel(GtkButton *button){ const MSList *configs; const char *host = NULL; int port=0; - + if (!tunnel) return; - + configs = linphone_tunnel_get_servers(tunnel); if(configs != NULL) { LinphoneTunnelConfig *ltc = (LinphoneTunnelConfig *)configs->data; @@ -1598,7 +1605,7 @@ void linphone_gtk_tunnel_ok(GtkButton *button){ gint http_port = (gint)gtk_spin_button_get_value(GTK_SPIN_BUTTON(linphone_gtk_get_widget(w,"http_port"))); const char *username=gtk_entry_get_text(GTK_ENTRY(linphone_gtk_get_widget(w,"username"))); const char *password=gtk_entry_get_text(GTK_ENTRY(linphone_gtk_get_widget(w,"password"))); - + if (tunnel==NULL) return; if (host && *host=='\0') host=NULL; if (http_port==0) http_port=8080; @@ -1608,7 +1615,7 @@ void linphone_gtk_tunnel_ok(GtkButton *button){ linphone_tunnel_add_server(tunnel, config); linphone_tunnel_enable(tunnel,enabled); linphone_tunnel_set_http_proxy(tunnel,http_host,http_port,username,password); - + gtk_widget_destroy(w); } @@ -1621,7 +1628,7 @@ static void show_dscp(GtkWidget *entry, int val){ char tmp[20]; snprintf(tmp,sizeof(tmp),"0x%x",val); gtk_entry_set_text(GTK_ENTRY(entry),tmp); - + } static int read_dscp(GtkWidget *entry){ @@ -1660,7 +1667,7 @@ void linphone_gtk_dscp_edit_response(GtkWidget *dialog, guint response_id){ read_dscp(linphone_gtk_get_widget(dialog,"audio_dscp"))); linphone_core_set_video_dscp(lc, read_dscp(linphone_gtk_get_widget(dialog,"video_dscp"))); - + break; default: break; From e15cc87d5ba153d34c037dafa1c926e0ef47126c Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 21 May 2014 17:29:15 +0200 Subject: [PATCH 16/94] Set RTCP information before starting the streams. --- coreapi/linphonecall.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index b91bf71c2..d077b6de2 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -1981,6 +1981,7 @@ static void linphone_call_start_audio_stream(LinphoneCall *call, const char *cna } } configure_rtp_session_for_rtcp_xr(lc, call, SalAudio); + audio_stream_set_rtcp_information(call->audiostream, cname, rtcp_tool); audio_stream_start_full( call->audiostream, call->audio_profile, @@ -2007,7 +2008,6 @@ static void linphone_call_start_audio_stream(LinphoneCall *call, const char *cna if (send_ringbacktone){ setup_ring_player(lc,call); } - audio_stream_set_rtcp_information(call->audiostream, cname, rtcp_tool); if (call->params.in_conference){ /*transform the graph to connect it to the conference filter */ @@ -2105,12 +2105,12 @@ static void linphone_call_start_video_stream(LinphoneCall *call, const char *cna video_stream_set_direction (call->videostream, dir); ms_message("%s lc rotation:%d\n", __FUNCTION__, lc->device_rotation); video_stream_set_device_rotation(call->videostream, lc->device_rotation); + video_stream_set_rtcp_information(call->videostream, cname, rtcp_tool); video_stream_start(call->videostream, call->video_profile, rtp_addr, vstream->rtp_port, rtcp_addr, linphone_core_rtcp_enabled(lc) ? (vstream->rtcp_port ? vstream->rtcp_port : vstream->rtp_port+1) : 0, used_pt, linphone_core_get_video_jittcomp(lc), cam); - video_stream_set_rtcp_information(call->videostream, cname,rtcp_tool); } }else ms_warning("No video stream accepted."); }else{ From 617522f78fa60e615576e8ff6fa67719a9894865 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 22 May 2014 09:54:01 +0200 Subject: [PATCH 17/94] Fix warning with linphone.png when starting linphone. --- gtk/main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gtk/main.c b/gtk/main.c index 2fd3fd897..d49fba466 100644 --- a/gtk/main.c +++ b/gtk/main.c @@ -2269,6 +2269,9 @@ int main(int argc, char *argv[]){ } } #endif + add_pixmap_directory("pixmaps"); + add_pixmap_directory(PACKAGE_DATA_DIR "/pixmaps/linphone"); + /* Now, look for the factory configuration file, we do it this late since we want to have had time to change directory and to parse the options, in case we needed to access the working directory */ @@ -2283,9 +2286,6 @@ int main(int argc, char *argv[]){ pbuf=create_pixbuf(icon_path); if (pbuf!=NULL) gtk_window_set_default_icon(pbuf); - add_pixmap_directory("pixmaps"); - add_pixmap_directory(PACKAGE_DATA_DIR "/pixmaps/linphone"); - #ifdef HAVE_GTK_OSX GtkosxApplication *theMacApp = gtkosx_application_get(); g_signal_connect(G_OBJECT(theMacApp),"NSApplicationDidBecomeActive",(GCallback)linphone_gtk_show_main_window,NULL); From fa44718513de0dfb22626d7d3841ad04fdd67dd0 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 22 May 2014 10:05:26 +0200 Subject: [PATCH 18/94] Fix icon in notifications. --- gtk/main.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gtk/main.c b/gtk/main.c index d49fba466..c1a52cda4 100644 --- a/gtk/main.c +++ b/gtk/main.c @@ -1313,12 +1313,16 @@ static bool_t notify_actions_supported() { return accepts_actions; } -static NotifyNotification* build_notification(const char *title, const char *body){ - return notify_notification_new(title,body,linphone_gtk_get_ui_config("icon",LINPHONE_ICON) +static NotifyNotification* build_notification(const char *title, const char *body) { + const char *icon_path = linphone_gtk_get_ui_config("icon", LINPHONE_ICON); + GdkPixbuf *pbuf = create_pixbuf(icon_path); + NotifyNotification *n = notify_notification_new(title, body, NULL #ifdef HAVE_NOTIFY1 - ,NULL + ,NULL #endif ); + notify_notification_set_icon_from_pixbuf(n, pbuf); + return n; } static void show_notification(NotifyNotification* n){ From 76a5f51e35a14778da2fc5ab9b0ce49186099e7e Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Thu, 22 May 2014 13:41:46 +0200 Subject: [PATCH 19/94] Use storage id for storing the message state, instead of filtering by message and time. This was historically used because notifications of the new message state would supposedly be made with a new instance of the same message, so without a correct storage_id. I verified that this is not the case anymore. --- coreapi/message_storage.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index 5a66393f4..b45b1afb1 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -156,12 +156,9 @@ unsigned int linphone_chat_message_store(LinphoneChatMessage *msg){ void linphone_chat_message_store_state(LinphoneChatMessage *msg){ LinphoneCore *lc=msg->chat_room->lc; if (lc->db){ - char *buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE message = %Q AND utc = %i;", - msg->state,msg->message,msg->time); - linphone_sql_request(lc->db,buf); + char *buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE id = %i;",msg->state,msg->storage_id); + linphone_sql_request(lc->db,buf); sqlite3_free(buf); - - } if( msg->state == LinphoneChatMessageStateDelivered From 43e42a55f54da89e09d02811f9fcdf21cd4b9f14 Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Thu, 22 May 2014 14:40:42 +0200 Subject: [PATCH 20/94] Better fix: just in case, don't use the storage id and use a better search condition. --- coreapi/message_storage.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index b45b1afb1..3676b5685 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -156,7 +156,8 @@ unsigned int linphone_chat_message_store(LinphoneChatMessage *msg){ void linphone_chat_message_store_state(LinphoneChatMessage *msg){ LinphoneCore *lc=msg->chat_room->lc; if (lc->db){ - char *buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE id = %i;",msg->state,msg->storage_id); + char *buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE (message = %Q OR url = %Q) AND utc = %i;", + msg->state,msg->message,msg->external_body_url,msg->time); linphone_sql_request(lc->db,buf); sqlite3_free(buf); } From d6e43a065b07296d7f20d4608e5031015fb30d91 Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Thu, 22 May 2014 16:55:03 +0200 Subject: [PATCH 21/94] remove GPLv3 notice from zRTP message in configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index b3c66dc98..2cf308b87 100644 --- a/configure.ac +++ b/configure.ac @@ -924,7 +924,7 @@ printf "* %-30s %s\n" "Account assistant" $build_wizard printf "* %-30s %s\n" "Console interface" $console_ui printf "* %-30s %s\n" "Tools" $build_tools printf "* %-30s %s\n" "Message storage" $enable_msg_storage -printf "* %-30s %s\n" "zRTP encryption (GPLv3)" $zrtp +printf "* %-30s %s\n" "zRTP encryption" $zrtp printf "* %-30s %s\n" "uPnP support" $build_upnp printf "* %-30s %s\n" "LDAP support" $enable_ldap From 23e9ed4a34d559e24e20abe1bee056077b5e677f Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Thu, 22 May 2014 19:07:11 +0200 Subject: [PATCH 22/94] update ms2 for conference bugfix --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index b6ad773d4..a99d6b0dc 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit b6ad773d4896b36fd707b6fc4f743b6b7df7325a +Subproject commit a99d6b0dc3f631a69e0aa46107213c5ebe45c1e3 From 063f0a4c19fab6dbf3f64854e8521ef6ff304e0a Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Fri, 23 May 2014 11:44:35 +0200 Subject: [PATCH 23/94] update ms2 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index a99d6b0dc..ce59013ae 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit a99d6b0dc3f631a69e0aa46107213c5ebe45c1e3 +Subproject commit ce59013ae65791bd9fecf18462477c94ab24655a From 3c770cac9a1253d1a8aaa4c57a6f2a6a917d539f Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 26 May 2014 10:10:02 +0200 Subject: [PATCH 24/94] Remove call to RTCP interval functions that no longer exist. --- coreapi/linphonecall.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index d077b6de2..04023c386 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -1880,15 +1880,6 @@ static void configure_rtp_session_for_rtcp_xr(LinphoneCore *lc, LinphoneCall *ca session = call->videostream->ms.sessions.rtp_session; } rtp_session_configure_rtcp_xr(session, ¤tconfig); - if (currentconfig.rcvr_rtt_mode != OrtpRtcpXrRcvrRttNone) { - rtp_session_set_rtcp_xr_rcvr_rtt_interval(session, lp_config_get_int(lc->config, "rtp", "rtcp_xr_rcvr_rtt_interval_duration", 5000)); - } - if (currentconfig.stat_summary_enabled == TRUE) { - rtp_session_set_rtcp_xr_stat_summary_interval(session, lp_config_get_int(lc->config, "rtp", "rtcp_xr_stat_summary_interval_duration", 5000)); - } - if (currentconfig.voip_metrics_enabled == TRUE) { - rtp_session_set_rtcp_xr_voip_metrics_interval(session, lp_config_get_int(lc->config, "rtp", "rtcp_xr_voip_metrics_interval_duration", 5000)); - } } static void linphone_call_start_audio_stream(LinphoneCall *call, const char *cname, bool_t muted, bool_t send_ringbacktone, bool_t use_arc){ From 0ddfbbb5745e0dc86ccfe5a35fb3b4cbb421c368 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 26 May 2014 10:10:49 +0200 Subject: [PATCH 25/94] Update oRTP and ms2 submodules for AVPF work. --- mediastreamer2 | 2 +- oRTP | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mediastreamer2 b/mediastreamer2 index ce59013ae..4da992ba5 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit ce59013ae65791bd9fecf18462477c94ab24655a +Subproject commit 4da992ba5695d51e84c832c20934c8c5bb73b3af diff --git a/oRTP b/oRTP index e4f28d143..902fd26d9 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit e4f28d1434b8f18061fb6a45a80649c4bc13cf9a +Subproject commit 902fd26d9c81665d48bea43e7771b3dc79f9e819 From 7b8a7a32ae15f965820bea51ed2b9e4af55907ad Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Mon, 26 May 2014 14:18:00 +0200 Subject: [PATCH 26/94] update ms2 and ortp for audio mixer bugfi --- mediastreamer2 | 2 +- oRTP | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mediastreamer2 b/mediastreamer2 index 4da992ba5..4d6563b75 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 4da992ba5695d51e84c832c20934c8c5bb73b3af +Subproject commit 4d6563b753e16e60fd5ece9bfa79c3be516ceaab diff --git a/oRTP b/oRTP index 902fd26d9..2f3ce6970 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 902fd26d9c81665d48bea43e7771b3dc79f9e819 +Subproject commit 2f3ce697093694165a31f024a3141cd89a95bc42 From 550af5a8c75fa0485918911f70ae1ce686395737 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Mon, 26 May 2014 22:10:05 +0200 Subject: [PATCH 27/94] fix linphone_proxy_config_edit() calls unbalanced with linphone_proxy_config_done() calls. This caused regressions, like for example linphone_core_clear_proxy_config() not sending any unREGISTER. --- coreapi/TunnelManager.cc | 110 ++------------------------------------- coreapi/proxy.c | 4 +- 2 files changed, 8 insertions(+), 106 deletions(-) diff --git a/coreapi/TunnelManager.cc b/coreapi/TunnelManager.cc index efac8fbb0..2bc8cd3e0 100644 --- a/coreapi/TunnelManager.cc +++ b/coreapi/TunnelManager.cc @@ -30,82 +30,6 @@ using namespace belledonnecomm; using namespace ::std; -#ifndef USE_BELLESIP - -Mutex TunnelManager::sMutex; - -int TunnelManager::eXosipSendto(int fd,const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen,void* userdata){ - TunnelManager* lTunnelMgr=(TunnelManager*)userdata; - - sMutex.lock(); - if (lTunnelMgr->mSipSocket==NULL){ - sMutex.unlock(); - return len; - } - lTunnelMgr->mSipSocket->sendto(buf,len,to,tolen); - sMutex.unlock(); - //ignore the error in all cases, retransmissions might be successful. - return len; -} - -int TunnelManager::eXosipRecvfrom(int fd, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen,void* userdata){ - TunnelManager* lTunnelMgr=(TunnelManager*)userdata; - int err; - sMutex.lock(); - if (lTunnelMgr->mSipSocket==NULL){ - sMutex.unlock(); - return 0;//let ignore the error - } - err=lTunnelMgr->mSipSocket->recvfrom(buf,len,from,*fromlen); - sMutex.unlock(); - return err; -} - -int TunnelManager::eXosipSelect(int max_fds, fd_set *s1, fd_set *s2, fd_set *s3, struct timeval *tv,void* userdata){ - struct timeval begin,cur; - TunnelManager* lTunnelMgr=(TunnelManager*)userdata; - if (s1 && tv!=0 && tv->tv_sec){ - /*this is the select from udp.c, the one that is interesting to us*/ - NativeSocket udp_fd=(NativeSocket)eXosip_get_udp_socket(); - NativeSocket controlfd=(NativeSocket)eXosip_get_control_fd(); - - FD_ZERO(s1); - gettimeofday(&begin,NULL); - do{ - struct timeval abit; - - abit.tv_sec=0; - abit.tv_usec=20000; - sMutex.lock(); - if (lTunnelMgr->mSipSocket!=NULL){ - if (lTunnelMgr->mSipSocket->hasData()) { - sMutex.unlock(); - /* we make exosip believe that it has udp data to read*/ - FD_SET(udp_fd,s1); - return 1; - } - } - sMutex.unlock(); - gettimeofday(&cur,NULL); - if (cur.tv_sec-begin.tv_sec>tv->tv_sec) { - FD_SET(controlfd,s1); - FD_SET(udp_fd,s1); - return 0; - } - FD_ZERO(s1); - FD_SET(controlfd,s1); - if (select(max_fds,s1,s2,s3,&abit)==1) { - return 1; - } - }while(1); - - }else{ - /*select called from other places, only the control fd is present */ - return select(max_fds,s1,s2,s3,tv); - } -} -#endif - void TunnelManager::addServer(const char *ip, int port,unsigned int udpMirrorPort,unsigned int delay) { if (ip == NULL) { @@ -186,10 +110,6 @@ void TunnelManager::start() { mTunnelClient->setHttpProxy(mHttpProxyHost.c_str(), mHttpProxyPort, mHttpUserName.c_str(), mHttpPasswd.c_str()); } mTunnelClient->start(); - -#ifndef USE_BELLESIP - if (mSipSocket == NULL) mSipSocket =mTunnelClient->createSocket(5060); -#endif } bool TunnelManager::isStarted() { @@ -217,9 +137,6 @@ int TunnelManager::customRecvfrom(struct _RtpTransport *t, mblk_t *msg, int flag TunnelManager::TunnelManager(LinphoneCore* lc) :TunnelClientController() ,mCore(lc) -#ifndef USE_BELLESIP -,mSipSocket(NULL) -#endif ,mCallback(NULL) ,mEnabled(false) ,mTunnelClient(NULL) @@ -227,12 +144,6 @@ TunnelManager::TunnelManager(LinphoneCore* lc) :TunnelClientController() ,mReady(false) ,mHttpProxyPort(0){ -#ifndef USE_BELLESIP - mExosipTransport.data=this; - mExosipTransport.recvfrom=eXosipRecvfrom; - mExosipTransport.sendto=eXosipSendto; - mExosipTransport.select=eXosipSelect; -#endif linphone_core_add_iterate_hook(mCore,(LinphoneCoreIterateHook)sOnIterate,this); mTransportFactories.audio_rtcp_func=sCreateRtpTransport; mTransportFactories.audio_rtcp_func_data=this; @@ -249,17 +160,7 @@ TunnelManager::~TunnelManager(){ } void TunnelManager::stopClient(){ -#ifdef USE_BELLESIP sal_disable_tunnel(mCore->sal); -#else - eXosip_transport_hook_register(NULL); - if (mSipSocket != NULL){ - sMutex.lock(); - mTunnelClient->closeSocket(mSipSocket); - mSipSocket = NULL; - sMutex.unlock(); - } -#endif if (mTunnelClient){ delete mTunnelClient; mTunnelClient=NULL; @@ -279,16 +180,11 @@ void TunnelManager::processTunnelEvent(const Event &ev){ //register if (lProxy) { - linphone_proxy_config_done(lProxy); + linphone_proxy_config_refresh_register(lProxy); } mReady=true; }else if (mEnabled && !mTunnelClient->isReady()){ /* we got disconnected from the tunnel */ - if (lProxy && linphone_proxy_config_is_registered(lProxy)) { - /*forces de-registration so that we register again when the tunnel is up*/ - linphone_proxy_config_edit(lProxy); - linphone_core_iterate(mCore); - } mReady=false; } } @@ -299,6 +195,8 @@ void TunnelManager::waitUnRegistration(){ if (lProxy && linphone_proxy_config_get_state(lProxy)==LinphoneRegistrationOk) { int i=0; linphone_proxy_config_edit(lProxy); + linphone_proxy_config_enable_register(lProxy,FALSE); + linphone_proxy_config_done(lProxy); //make sure unregister is sent and authenticated do{ linphone_core_iterate(mCore); @@ -348,6 +246,8 @@ void TunnelManager::enable(bool isEnable) { LinphoneProxyConfig* lProxy; linphone_core_get_default_proxy(mCore, &lProxy); if (lProxy) { + linphone_proxy_config_edit(lProxy); + linphone_proxy_config_enable_register(lProxy,TRUE); linphone_proxy_config_done(lProxy); } diff --git a/coreapi/proxy.c b/coreapi/proxy.c index d7b99aeb2..ea021639a 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -1081,8 +1081,10 @@ void linphone_core_remove_proxy_config(LinphoneCore *lc, LinphoneProxyConfig *cf lc->sip_conf.deleted_proxies=ms_list_append(lc->sip_conf.deleted_proxies,cfg); cfg->deletion_date=ms_time(NULL); if (cfg->state==LinphoneRegistrationOk){ - /* this will unREGISTER */ + /* unREGISTER */ linphone_proxy_config_edit(cfg); + linphone_proxy_config_enable_register(cfg,FALSE); + linphone_proxy_config_done(cfg); } if (lc->default_proxy==cfg){ lc->default_proxy=NULL; From d8e6070066f810151755ae088374abe4a3ff8a20 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 27 May 2014 10:34:03 +0200 Subject: [PATCH 28/94] Update ms2 submodule. --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 4d6563b75..6ce0bcea9 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 4d6563b753e16e60fd5ece9bfa79c3be516ceaab +Subproject commit 6ce0bcea96b6e91c856e9a9dbab5b7bf8720ba9f From dbbea2a41c2926494be46ee2f85acdaa9fb36550 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Wed, 28 May 2014 11:34:59 +0200 Subject: [PATCH 29/94] add debug message to indicate duration of sqlite request --- coreapi/message_storage.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index 3676b5685..ee9b0efb0 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -242,6 +242,7 @@ MSList *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){ MSList *ret; char *buf; char *peer; + uint64_t begin,end; if (lc->db==NULL) return NULL; peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); @@ -250,7 +251,10 @@ MSList *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){ buf=sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC LIMIT %i ;",peer,nb_message); else buf=sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC;",peer); + begin=ortp_get_cur_time_ms(); linphone_sql_request_message(lc->db,buf,cr); + end=ortp_get_cur_time_ms(); + ms_message("linphone_chat_room_get_history(): completed in %i ms",(int)(end-begin)); sqlite3_free(buf); ret=cr->messages_hist; cr->messages_hist=NULL; @@ -327,13 +331,15 @@ static int migrate_messages(void* data,int argc, char** argv, char** column_name static void linphone_migrate_timestamps(sqlite3* db){ int ret; char* errmsg = NULL; + uint64_t begin=ortp_get_cur_time_ms(); ret = sqlite3_exec(db,"SELECT id,time,direction FROM history WHERE time != '-1'", migrate_messages, db, &errmsg); if( ret != SQLITE_OK ){ ms_warning("Error migrating outgoing messages: %s.\n", errmsg); sqlite3_free(errmsg); } else { - ms_message("Migrated message timestamps to UTC"); + uint64_t end=ortp_get_cur_time_ms(); + ms_message("Migrated message timestamps to UTC in %i ms",(int)(end-begin)); } } From 1a89e8a1746d2bae7524756029937ff42dac2229 Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Wed, 28 May 2014 14:26:14 +0200 Subject: [PATCH 30/94] Use a transaction for message migration. On 10000 messages, we have a 20x speedup. --- coreapi/message_storage.c | 896 +++++++++++++++++++------------------- 1 file changed, 450 insertions(+), 446 deletions(-) diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index ee9b0efb0..8a04d1ad2 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -1,446 +1,450 @@ -/* -message_storage.c -Copyright (C) 2012 Belledonne Communications, Grenoble, France - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "private.h" -#include "linphonecore.h" - -#ifdef WIN32 - -static inline char *my_ctime_r(const time_t *t, char *buf){ - strcpy(buf,ctime(t)); - return buf; -} - -#else -#define my_ctime_r ctime_r -#endif - -#ifdef MSG_STORAGE_ENABLED - -#include "sqlite3.h" - -static inline LinphoneChatMessage* get_transient_message(LinphoneChatRoom* cr, unsigned int storage_id){ - MSList* transients = cr->transient_messages; - LinphoneChatMessage* chat; - while( transients ){ - chat = (LinphoneChatMessage*)transients->data; - if(chat->storage_id == storage_id){ - return linphone_chat_message_ref(chat); - } - transients = transients->next; - } - return NULL; -} - -static void create_chat_message(char **argv, void *data){ - LinphoneChatRoom *cr = (LinphoneChatRoom *)data; - LinphoneAddress *from; - - unsigned int storage_id = atoi(argv[0]); - - // check if the message exists in the transient list, in which case we should return that one. - LinphoneChatMessage* new_message = get_transient_message(cr, storage_id); - if( new_message == NULL ){ - new_message = linphone_chat_room_create_message(cr, argv[4]); - - if(atoi(argv[3])==LinphoneChatMessageIncoming){ - new_message->dir=LinphoneChatMessageIncoming; - from=linphone_address_new(argv[2]); - } else { - new_message->dir=LinphoneChatMessageOutgoing; - from=linphone_address_new(argv[1]); - } - linphone_chat_message_set_from(new_message,from); - linphone_address_destroy(from); - - if( argv[9] != NULL ){ - new_message->time = (time_t)atol(argv[9]); - } else { - new_message->time = time(NULL); - } - - new_message->is_read=atoi(argv[6]); - new_message->state=atoi(argv[7]); - new_message->storage_id=storage_id; - new_message->external_body_url=argv[8]?ms_strdup(argv[8]):NULL; - } - cr->messages_hist=ms_list_prepend(cr->messages_hist,new_message); -} - -// Called when fetching all conversations from database -static int callback_all(void *data, int argc, char **argv, char **colName){ - LinphoneCore* lc = (LinphoneCore*) data; - char* address = argv[0]; - linphone_core_get_or_create_chat_room(lc, address); - return 0; -} - -static int callback(void *data, int argc, char **argv, char **colName){ - create_chat_message(argv,data); - return 0; -} - -void linphone_sql_request_message(sqlite3 *db,const char *stmt,LinphoneChatRoom *cr){ - char* errmsg=NULL; - int ret; - ret=sqlite3_exec(db,stmt,callback,cr,&errmsg); - if(ret != SQLITE_OK) { - ms_error("Error in creation: %s.\n", errmsg); - sqlite3_free(errmsg); - } -} - -void linphone_sql_request(sqlite3* db,const char *stmt){ - char* errmsg=NULL; - int ret; - ret=sqlite3_exec(db,stmt,NULL,NULL,&errmsg); - if(ret != SQLITE_OK) { - ms_error("linphone_sql_request: error sqlite3_exec(): %s.\n", errmsg); - sqlite3_free(errmsg); - } -} - -// Process the request to fetch all chat contacts -void linphone_sql_request_all(sqlite3* db,const char *stmt, LinphoneCore* lc){ - char* errmsg=NULL; - int ret; - ret=sqlite3_exec(db,stmt,callback_all,lc,&errmsg); - if(ret != SQLITE_OK) { - ms_error("linphone_sql_request_all: error sqlite3_exec(): %s.\n", errmsg); - sqlite3_free(errmsg); - } -} - -unsigned int linphone_chat_message_store(LinphoneChatMessage *msg){ - LinphoneCore *lc=linphone_chat_room_get_lc(msg->chat_room); - int id=0; - - if (lc->db){ - char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(msg->chat_room)); - char *local_contact=linphone_address_as_string_uri_only(linphone_chat_message_get_local_address(msg)); - char *buf=sqlite3_mprintf("INSERT INTO history VALUES(NULL,%Q,%Q,%i,%Q,%Q,%i,%i,%Q,%i);", - local_contact, - peer, - msg->dir, - msg->message, - "-1", /* use UTC field now */ - msg->is_read, - msg->state, - msg->external_body_url, - msg->time); - linphone_sql_request(lc->db,buf); - sqlite3_free(buf); - ms_free(local_contact); - ms_free(peer); - id = (unsigned int) sqlite3_last_insert_rowid (lc->db); - } - return id; -} - -void linphone_chat_message_store_state(LinphoneChatMessage *msg){ - LinphoneCore *lc=msg->chat_room->lc; - if (lc->db){ - char *buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE (message = %Q OR url = %Q) AND utc = %i;", - msg->state,msg->message,msg->external_body_url,msg->time); - linphone_sql_request(lc->db,buf); - sqlite3_free(buf); - } - - if( msg->state == LinphoneChatMessageStateDelivered - || msg->state == LinphoneChatMessageStateNotDelivered ){ - // message is not transient anymore, we can remove it from our transient list: - msg->chat_room->transient_messages = ms_list_remove(msg->chat_room->transient_messages, msg); - linphone_chat_message_unref(msg); - } -} - -void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){ - LinphoneCore *lc=linphone_chat_room_get_lc(cr); - int read=1; - - if (lc->db==NULL) return ; - - char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); - char *buf=sqlite3_mprintf("UPDATE history SET read=%i WHERE remoteContact = %Q;", - read,peer); - linphone_sql_request(lc->db,buf); - sqlite3_free(buf); - ms_free(peer); -} - -void linphone_chat_room_update_url(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { - LinphoneCore *lc=linphone_chat_room_get_lc(cr); - - if (lc->db==NULL) return ; - - char *buf=sqlite3_mprintf("UPDATE history SET url=%Q WHERE id=%i;",msg->external_body_url,msg->storage_id); - linphone_sql_request(lc->db,buf); - sqlite3_free(buf); -} - -int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){ - LinphoneCore *lc=linphone_chat_room_get_lc(cr); - int numrows=0; - - if (lc->db==NULL) return 0; - - char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); - char *buf=sqlite3_mprintf("SELECT count(*) FROM history WHERE remoteContact = %Q AND read = 0;",peer); - sqlite3_stmt *selectStatement; - int returnValue = sqlite3_prepare_v2(lc->db,buf,-1,&selectStatement,NULL); - if (returnValue == SQLITE_OK){ - if(sqlite3_step(selectStatement) == SQLITE_ROW){ - numrows= sqlite3_column_int(selectStatement, 0); - } - } - sqlite3_finalize(selectStatement); - sqlite3_free(buf); - ms_free(peer); - return numrows; -} - -void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { - LinphoneCore *lc=cr->lc; - - if (lc->db==NULL) return ; - - char *buf=sqlite3_mprintf("DELETE FROM history WHERE id = %i;", msg->storage_id); - linphone_sql_request(lc->db,buf); - sqlite3_free(buf); -} - -void linphone_chat_room_delete_history(LinphoneChatRoom *cr){ - LinphoneCore *lc=cr->lc; - - if (lc->db==NULL) return ; - - char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); - char *buf=sqlite3_mprintf("DELETE FROM history WHERE remoteContact = %Q;",peer); - linphone_sql_request(lc->db,buf); - sqlite3_free(buf); - ms_free(peer); -} - -MSList *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){ - LinphoneCore *lc=linphone_chat_room_get_lc(cr); - MSList *ret; - char *buf; - char *peer; - uint64_t begin,end; - - if (lc->db==NULL) return NULL; - peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); - cr->messages_hist = NULL; - if (nb_message > 0) - buf=sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC LIMIT %i ;",peer,nb_message); - else - buf=sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC;",peer); - begin=ortp_get_cur_time_ms(); - linphone_sql_request_message(lc->db,buf,cr); - end=ortp_get_cur_time_ms(); - ms_message("linphone_chat_room_get_history(): completed in %i ms",(int)(end-begin)); - sqlite3_free(buf); - ret=cr->messages_hist; - cr->messages_hist=NULL; - ms_free(peer); - return ret; -} - -void linphone_close_storage(sqlite3* db){ - sqlite3_close(db); -} - -void linphone_create_table(sqlite3* db){ - char* errmsg=NULL; - int ret; - ret=sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS history (" - "id INTEGER PRIMARY KEY AUTOINCREMENT," - "localContact TEXT NOT NULL," - "remoteContact TEXT NOT NULL," - "direction INTEGER," - "message TEXT," - "time TEXT NOT NULL," - "read INTEGER," - "status INTEGER" - ");", - 0,0,&errmsg); - if(ret != SQLITE_OK) { - ms_error("Error in creation: %s.\n", errmsg); - sqlite3_free(errmsg); - } -} - - -static const char *days[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; -static const char *months[]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; -static time_t parse_time_from_db( const char* time ){ - /* messages used to be stored in the DB by using string-based time */ - struct tm ret={0}; - char tmp1[80]={0}; - char tmp2[80]={0}; - int i,j; - time_t parsed = 0; - - if( sscanf(time,"%3c %3c%d%d:%d:%d %d",tmp1,tmp2,&ret.tm_mday, - &ret.tm_hour,&ret.tm_min,&ret.tm_sec,&ret.tm_year) == 7 ){ - ret.tm_year-=1900; - for(i=0;i<7;i++) { - if(strcmp(tmp1,days[i])==0) ret.tm_wday=i; - } - for(j=0;j<12;j++) { - if(strcmp(tmp2,months[j])==0) ret.tm_mon=j; - } - ret.tm_isdst=-1; - parsed = mktime(&ret); - } - return parsed; -} - - -static int migrate_messages(void* data,int argc, char** argv, char** column_names) { - time_t new_time = parse_time_from_db(argv[1]); - if( new_time ){ - /* replace 'time' by -1 and set 'utc' to the timestamp */ - char *buf = sqlite3_mprintf("UPDATE history SET utc=%i,time='-1' WHERE id=%i", new_time, atoi(argv[0])); - if( buf) { - linphone_sql_request((sqlite3*)data, buf); - sqlite3_free(buf); - } - } else { - ms_warning("Cannot parse time %s from id %s", argv[1], argv[0]); - } - return 0; -} - -static void linphone_migrate_timestamps(sqlite3* db){ - int ret; - char* errmsg = NULL; - uint64_t begin=ortp_get_cur_time_ms(); - - ret = sqlite3_exec(db,"SELECT id,time,direction FROM history WHERE time != '-1'", migrate_messages, db, &errmsg); - if( ret != SQLITE_OK ){ - ms_warning("Error migrating outgoing messages: %s.\n", errmsg); - sqlite3_free(errmsg); - } else { - uint64_t end=ortp_get_cur_time_ms(); - ms_message("Migrated message timestamps to UTC in %i ms",(int)(end-begin)); - } -} - -void linphone_update_table(sqlite3* db) { - char* errmsg=NULL; - int ret; - - // for image url storage - ret=sqlite3_exec(db,"ALTER TABLE history ADD COLUMN url TEXT;",NULL,NULL,&errmsg); - if(ret != SQLITE_OK) { - ms_message("Table already up to date: %s.", errmsg); - sqlite3_free(errmsg); - } else { - ms_debug("Table updated successfully for URL."); - } - - // for UTC timestamp storage - ret = sqlite3_exec(db, "ALTER TABLE history ADD COLUMN utc INTEGER;", NULL,NULL,&errmsg); - if( ret != SQLITE_OK ){ - ms_message("Table already up to date: %s.", errmsg); - sqlite3_free(errmsg); - } else { - ms_debug("Table updated successfully for UTC."); - // migrate from old text-based timestamps to unix time-based timestamps - linphone_migrate_timestamps(db); - } -} - -void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) { - char *buf; - - if (lc->db==NULL) return; - buf=sqlite3_mprintf("SELECT remoteContact FROM history GROUP BY remoteContact;"); - linphone_sql_request_all(lc->db,buf,lc); - sqlite3_free(buf); -} - -void linphone_core_message_storage_init(LinphoneCore *lc){ - int ret; - const char *errmsg; - sqlite3 *db; - - linphone_core_message_storage_close(lc); - - ret=sqlite3_open(lc->chat_db_file,&db); - if(ret != SQLITE_OK) { - errmsg=sqlite3_errmsg(db); - ms_error("Error in the opening: %s.\n", errmsg); - sqlite3_close(db); - } - linphone_create_table(db); - linphone_update_table(db); - lc->db=db; - - // Create a chatroom for each contact in the chat history - linphone_message_storage_init_chat_rooms(lc); -} - -void linphone_core_message_storage_close(LinphoneCore *lc){ - if (lc->db){ - sqlite3_close(lc->db); - lc->db=NULL; - } -} - -#else - -unsigned int linphone_chat_message_store(LinphoneChatMessage *cr){ - return 0; -} - -void linphone_chat_message_store_state(LinphoneChatMessage *cr){ -} - -void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){ -} - -MSList *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){ - return NULL; -} - -void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { -} - -void linphone_chat_room_delete_history(LinphoneChatRoom *cr){ -} - -void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) { -} - -void linphone_core_message_storage_init(LinphoneCore *lc){ -} - -void linphone_core_message_storage_close(LinphoneCore *lc){ -} - -void linphone_chat_room_update_url(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { -} - -int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){ - return 0; -} - -#endif +/* +message_storage.c +Copyright (C) 2012 Belledonne Communications, Grenoble, France + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "private.h" +#include "linphonecore.h" + +#ifdef WIN32 + +static inline char *my_ctime_r(const time_t *t, char *buf){ + strcpy(buf,ctime(t)); + return buf; +} + +#else +#define my_ctime_r ctime_r +#endif + +#ifdef MSG_STORAGE_ENABLED + +#include "sqlite3.h" + +static inline LinphoneChatMessage* get_transient_message(LinphoneChatRoom* cr, unsigned int storage_id){ + MSList* transients = cr->transient_messages; + LinphoneChatMessage* chat; + while( transients ){ + chat = (LinphoneChatMessage*)transients->data; + if(chat->storage_id == storage_id){ + return linphone_chat_message_ref(chat); + } + transients = transients->next; + } + return NULL; +} + +static void create_chat_message(char **argv, void *data){ + LinphoneChatRoom *cr = (LinphoneChatRoom *)data; + LinphoneAddress *from; + + unsigned int storage_id = atoi(argv[0]); + + // check if the message exists in the transient list, in which case we should return that one. + LinphoneChatMessage* new_message = get_transient_message(cr, storage_id); + if( new_message == NULL ){ + new_message = linphone_chat_room_create_message(cr, argv[4]); + + if(atoi(argv[3])==LinphoneChatMessageIncoming){ + new_message->dir=LinphoneChatMessageIncoming; + from=linphone_address_new(argv[2]); + } else { + new_message->dir=LinphoneChatMessageOutgoing; + from=linphone_address_new(argv[1]); + } + linphone_chat_message_set_from(new_message,from); + linphone_address_destroy(from); + + if( argv[9] != NULL ){ + new_message->time = (time_t)atol(argv[9]); + } else { + new_message->time = time(NULL); + } + + new_message->is_read=atoi(argv[6]); + new_message->state=atoi(argv[7]); + new_message->storage_id=storage_id; + new_message->external_body_url=argv[8]?ms_strdup(argv[8]):NULL; + } + cr->messages_hist=ms_list_prepend(cr->messages_hist,new_message); +} + +// Called when fetching all conversations from database +static int callback_all(void *data, int argc, char **argv, char **colName){ + LinphoneCore* lc = (LinphoneCore*) data; + char* address = argv[0]; + linphone_core_get_or_create_chat_room(lc, address); + return 0; +} + +static int callback(void *data, int argc, char **argv, char **colName){ + create_chat_message(argv,data); + return 0; +} + +void linphone_sql_request_message(sqlite3 *db,const char *stmt,LinphoneChatRoom *cr){ + char* errmsg=NULL; + int ret; + ret=sqlite3_exec(db,stmt,callback,cr,&errmsg); + if(ret != SQLITE_OK) { + ms_error("Error in creation: %s.\n", errmsg); + sqlite3_free(errmsg); + } +} + +void linphone_sql_request(sqlite3* db,const char *stmt){ + char* errmsg=NULL; + int ret; + ret=sqlite3_exec(db,stmt,NULL,NULL,&errmsg); + if(ret != SQLITE_OK) { + ms_error("linphone_sql_request: error sqlite3_exec(): %s.\n", errmsg); + sqlite3_free(errmsg); + } +} + +// Process the request to fetch all chat contacts +void linphone_sql_request_all(sqlite3* db,const char *stmt, LinphoneCore* lc){ + char* errmsg=NULL; + int ret; + ret=sqlite3_exec(db,stmt,callback_all,lc,&errmsg); + if(ret != SQLITE_OK) { + ms_error("linphone_sql_request_all: error sqlite3_exec(): %s.\n", errmsg); + sqlite3_free(errmsg); + } +} + +unsigned int linphone_chat_message_store(LinphoneChatMessage *msg){ + LinphoneCore *lc=linphone_chat_room_get_lc(msg->chat_room); + int id=0; + + if (lc->db){ + char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(msg->chat_room)); + char *local_contact=linphone_address_as_string_uri_only(linphone_chat_message_get_local_address(msg)); + char *buf=sqlite3_mprintf("INSERT INTO history VALUES(NULL,%Q,%Q,%i,%Q,%Q,%i,%i,%Q,%i);", + local_contact, + peer, + msg->dir, + msg->message, + "-1", /* use UTC field now */ + msg->is_read, + msg->state, + msg->external_body_url, + msg->time); + linphone_sql_request(lc->db,buf); + sqlite3_free(buf); + ms_free(local_contact); + ms_free(peer); + id = (unsigned int) sqlite3_last_insert_rowid (lc->db); + } + return id; +} + +void linphone_chat_message_store_state(LinphoneChatMessage *msg){ + LinphoneCore *lc=msg->chat_room->lc; + if (lc->db){ + char *buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE (message = %Q OR url = %Q) AND utc = %i;", + msg->state,msg->message,msg->external_body_url,msg->time); + linphone_sql_request(lc->db,buf); + sqlite3_free(buf); + } + + if( msg->state == LinphoneChatMessageStateDelivered + || msg->state == LinphoneChatMessageStateNotDelivered ){ + // message is not transient anymore, we can remove it from our transient list: + msg->chat_room->transient_messages = ms_list_remove(msg->chat_room->transient_messages, msg); + linphone_chat_message_unref(msg); + } +} + +void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){ + LinphoneCore *lc=linphone_chat_room_get_lc(cr); + int read=1; + + if (lc->db==NULL) return ; + + char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); + char *buf=sqlite3_mprintf("UPDATE history SET read=%i WHERE remoteContact = %Q;", + read,peer); + linphone_sql_request(lc->db,buf); + sqlite3_free(buf); + ms_free(peer); +} + +void linphone_chat_room_update_url(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { + LinphoneCore *lc=linphone_chat_room_get_lc(cr); + + if (lc->db==NULL) return ; + + char *buf=sqlite3_mprintf("UPDATE history SET url=%Q WHERE id=%i;",msg->external_body_url,msg->storage_id); + linphone_sql_request(lc->db,buf); + sqlite3_free(buf); +} + +int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){ + LinphoneCore *lc=linphone_chat_room_get_lc(cr); + int numrows=0; + + if (lc->db==NULL) return 0; + + char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); + char *buf=sqlite3_mprintf("SELECT count(*) FROM history WHERE remoteContact = %Q AND read = 0;",peer); + sqlite3_stmt *selectStatement; + int returnValue = sqlite3_prepare_v2(lc->db,buf,-1,&selectStatement,NULL); + if (returnValue == SQLITE_OK){ + if(sqlite3_step(selectStatement) == SQLITE_ROW){ + numrows= sqlite3_column_int(selectStatement, 0); + } + } + sqlite3_finalize(selectStatement); + sqlite3_free(buf); + ms_free(peer); + return numrows; +} + +void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { + LinphoneCore *lc=cr->lc; + + if (lc->db==NULL) return ; + + char *buf=sqlite3_mprintf("DELETE FROM history WHERE id = %i;", msg->storage_id); + linphone_sql_request(lc->db,buf); + sqlite3_free(buf); +} + +void linphone_chat_room_delete_history(LinphoneChatRoom *cr){ + LinphoneCore *lc=cr->lc; + + if (lc->db==NULL) return ; + + char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); + char *buf=sqlite3_mprintf("DELETE FROM history WHERE remoteContact = %Q;",peer); + linphone_sql_request(lc->db,buf); + sqlite3_free(buf); + ms_free(peer); +} + +MSList *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){ + LinphoneCore *lc=linphone_chat_room_get_lc(cr); + MSList *ret; + char *buf; + char *peer; + uint64_t begin,end; + + if (lc->db==NULL) return NULL; + peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); + cr->messages_hist = NULL; + if (nb_message > 0) + buf=sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC LIMIT %i ;",peer,nb_message); + else + buf=sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC;",peer); + begin=ortp_get_cur_time_ms(); + linphone_sql_request_message(lc->db,buf,cr); + end=ortp_get_cur_time_ms(); + ms_message("linphone_chat_room_get_history(): completed in %i ms",(int)(end-begin)); + sqlite3_free(buf); + ret=cr->messages_hist; + cr->messages_hist=NULL; + ms_free(peer); + return ret; +} + +void linphone_close_storage(sqlite3* db){ + sqlite3_close(db); +} + +void linphone_create_table(sqlite3* db){ + char* errmsg=NULL; + int ret; + ret=sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS history (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "localContact TEXT NOT NULL," + "remoteContact TEXT NOT NULL," + "direction INTEGER," + "message TEXT," + "time TEXT NOT NULL," + "read INTEGER," + "status INTEGER" + ");", + 0,0,&errmsg); + if(ret != SQLITE_OK) { + ms_error("Error in creation: %s.\n", errmsg); + sqlite3_free(errmsg); + } +} + + +static const char *days[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; +static const char *months[]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; +static time_t parse_time_from_db( const char* time ){ + /* messages used to be stored in the DB by using string-based time */ + struct tm ret={0}; + char tmp1[80]={0}; + char tmp2[80]={0}; + int i,j; + time_t parsed = 0; + + if( sscanf(time,"%3c %3c%d%d:%d:%d %d",tmp1,tmp2,&ret.tm_mday, + &ret.tm_hour,&ret.tm_min,&ret.tm_sec,&ret.tm_year) == 7 ){ + ret.tm_year-=1900; + for(i=0;i<7;i++) { + if(strcmp(tmp1,days[i])==0) ret.tm_wday=i; + } + for(j=0;j<12;j++) { + if(strcmp(tmp2,months[j])==0) ret.tm_mon=j; + } + ret.tm_isdst=-1; + parsed = mktime(&ret); + } + return parsed; +} + + +static int migrate_messages(void* data,int argc, char** argv, char** column_names) { + time_t new_time = parse_time_from_db(argv[1]); + if( new_time ){ + /* replace 'time' by -1 and set 'utc' to the timestamp */ + char *buf = sqlite3_mprintf("UPDATE history SET utc=%i,time='-1' WHERE id=%i", new_time, atoi(argv[0])); + if( buf) { + linphone_sql_request((sqlite3*)data, buf); + sqlite3_free(buf); + } + } else { + ms_warning("Cannot parse time %s from id %s", argv[1], argv[0]); + } + return 0; +} + +static void linphone_migrate_timestamps(sqlite3* db){ + int ret; + char* errmsg = NULL; + uint64_t begin=ortp_get_cur_time_ms(); + + linphone_sql_request(db,"BEGIN TRANSACTION"); + + ret = sqlite3_exec(db,"SELECT id,time,direction FROM history WHERE time != '-1'", migrate_messages, db, &errmsg); + if( ret != SQLITE_OK ){ + ms_warning("Error migrating outgoing messages: %s.\n", errmsg); + sqlite3_free(errmsg); + linphone_sql_request(db, "ROLLBACK"); + } else { + linphone_sql_request(db, "COMMIT"); + uint64_t end=ortp_get_cur_time_ms(); + ms_message("Migrated message timestamps to UTC in %i ms",(int)(end-begin)); + } +} + +void linphone_update_table(sqlite3* db) { + char* errmsg=NULL; + int ret; + + // for image url storage + ret=sqlite3_exec(db,"ALTER TABLE history ADD COLUMN url TEXT;",NULL,NULL,&errmsg); + if(ret != SQLITE_OK) { + ms_message("Table already up to date: %s.", errmsg); + sqlite3_free(errmsg); + } else { + ms_debug("Table updated successfully for URL."); + } + + // for UTC timestamp storage + ret = sqlite3_exec(db, "ALTER TABLE history ADD COLUMN utc INTEGER;", NULL,NULL,&errmsg); + if( ret != SQLITE_OK ){ + ms_message("Table already up to date: %s.", errmsg); + sqlite3_free(errmsg); + } else { + ms_debug("Table updated successfully for UTC."); + // migrate from old text-based timestamps to unix time-based timestamps + linphone_migrate_timestamps(db); + } +} + +void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) { + char *buf; + + if (lc->db==NULL) return; + buf=sqlite3_mprintf("SELECT remoteContact FROM history GROUP BY remoteContact;"); + linphone_sql_request_all(lc->db,buf,lc); + sqlite3_free(buf); +} + +void linphone_core_message_storage_init(LinphoneCore *lc){ + int ret; + const char *errmsg; + sqlite3 *db; + + linphone_core_message_storage_close(lc); + + ret=sqlite3_open(lc->chat_db_file,&db); + if(ret != SQLITE_OK) { + errmsg=sqlite3_errmsg(db); + ms_error("Error in the opening: %s.\n", errmsg); + sqlite3_close(db); + } + linphone_create_table(db); + linphone_update_table(db); + lc->db=db; + + // Create a chatroom for each contact in the chat history + linphone_message_storage_init_chat_rooms(lc); +} + +void linphone_core_message_storage_close(LinphoneCore *lc){ + if (lc->db){ + sqlite3_close(lc->db); + lc->db=NULL; + } +} + +#else + +unsigned int linphone_chat_message_store(LinphoneChatMessage *cr){ + return 0; +} + +void linphone_chat_message_store_state(LinphoneChatMessage *cr){ +} + +void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){ +} + +MSList *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){ + return NULL; +} + +void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { +} + +void linphone_chat_room_delete_history(LinphoneChatRoom *cr){ +} + +void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) { +} + +void linphone_core_message_storage_init(LinphoneCore *lc){ +} + +void linphone_core_message_storage_close(LinphoneCore *lc){ +} + +void linphone_chat_room_update_url(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { +} + +int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){ + return 0; +} + +#endif From b56095f59ffc345f087a935ae5d24cbefca1b014 Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Wed, 28 May 2014 14:53:30 +0200 Subject: [PATCH 31/94] Add API to debug sqlite timings, and a test to profile the message migration --- coreapi/message_storage.c | 39 ++++++++---- coreapi/private.h | 2 + tester/Makefile.am | 2 +- tester/message_tester.c | 121 +++++++++++++++++++++++++++++++++----- tester/messages.db | Bin 0 -> 1821696 bytes 5 files changed, 135 insertions(+), 29 deletions(-) create mode 100644 tester/messages.db diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index 8a04d1ad2..86887c7b7 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -20,17 +20,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "private.h" #include "linphonecore.h" -#ifdef WIN32 - -static inline char *my_ctime_r(const time_t *t, char *buf){ - strcpy(buf,ctime(t)); - return buf; -} - -#else -#define my_ctime_r ctime_r -#endif - #ifdef MSG_STORAGE_ENABLED #include "sqlite3.h" @@ -157,8 +146,8 @@ void linphone_chat_message_store_state(LinphoneChatMessage *msg){ LinphoneCore *lc=msg->chat_room->lc; if (lc->db){ char *buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE (message = %Q OR url = %Q) AND utc = %i;", - msg->state,msg->message,msg->external_body_url,msg->time); - linphone_sql_request(lc->db,buf); + msg->state,msg->message,msg->external_body_url,msg->time); + linphone_sql_request(lc->db,buf); sqlite3_free(buf); } @@ -381,6 +370,27 @@ void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) { sqlite3_free(buf); } +static void _linphone_message_storage_profile(void*data,const char*statement, sqlite3_uint64 duration){ + ms_warning("SQL statement '%s' took %" PRId64 " microseconds", statement, duration / 1000 ); +} + +static void linphone_message_storage_activate_debug(sqlite3* db, bool_t debug){ + if( debug ){ + sqlite3_profile(db, _linphone_message_storage_profile, NULL ); + } else { + sqlite3_profile(db, NULL, NULL ); + } +} + +void linphone_core_message_storage_set_debug(LinphoneCore *lc, bool_t debug){ + + lc->debug_storage = debug; + + if( lc->db ){ + linphone_message_storage_activate_debug(lc->db, debug); + } +} + void linphone_core_message_storage_init(LinphoneCore *lc){ int ret; const char *errmsg; @@ -394,6 +404,9 @@ void linphone_core_message_storage_init(LinphoneCore *lc){ ms_error("Error in the opening: %s.\n", errmsg); sqlite3_close(db); } + + linphone_message_storage_activate_debug(db, lc->debug_storage); + linphone_create_table(db); linphone_update_table(db); lc->db=db; diff --git a/coreapi/private.h b/coreapi/private.h index d43f6454f..a2fb3d9c2 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -701,6 +701,7 @@ struct _LinphoneCore char *chat_db_file; #ifdef MSG_STORAGE_ENABLED sqlite3 *db; + bool_t debug_storage; #endif #ifdef BUILD_UPNP UpnpContext *upnp; @@ -815,6 +816,7 @@ void linphone_message_storage_init_chat_rooms(LinphoneCore *lc); void linphone_chat_message_store_state(LinphoneChatMessage *msg); void linphone_core_message_storage_init(LinphoneCore *lc); void linphone_core_message_storage_close(LinphoneCore *lc); +void linphone_core_message_storage_set_debug(LinphoneCore *lc, bool_t debug); void linphone_core_play_named_tone(LinphoneCore *lc, LinphoneToneID id); bool_t linphone_core_tone_indications_enabled(LinphoneCore*lc); diff --git a/tester/Makefile.am b/tester/Makefile.am index 1cda152c7..46ffb12cd 100644 --- a/tester/Makefile.am +++ b/tester/Makefile.am @@ -26,7 +26,7 @@ liblinphonetester_la_LDFLAGS= -no-undefined liblinphonetester_la_LIBADD= ../coreapi/liblinphone.la $(CUNIT_LIBS) AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/coreapi -AM_CFLAGS = $(STRICT_OPTIONS) -DIN_LINPHONE $(ORTP_CFLAGS) $(MEDIASTREAMER_CFLAGS) $(CUNIT_CFLAGS) $(BELLESIP_CFLAGS) $(LIBXML2_CFLAGS) +AM_CFLAGS = $(STRICT_OPTIONS) -DIN_LINPHONE $(ORTP_CFLAGS) $(MEDIASTREAMER_CFLAGS) $(CUNIT_CFLAGS) $(BELLESIP_CFLAGS) $(LIBXML2_CFLAGS) $(SQLITE3_CFLAGS) if !BUILD_IOS diff --git a/tester/message_tester.c b/tester/message_tester.c index 6aa884616..b5b8934e4 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -1,19 +1,19 @@ /* - liblinphone_tester - liblinphone test suite - Copyright (C) 2013 Belledonne Communications SARL + liblinphone_tester - liblinphone test suite + Copyright (C) 2013 Belledonne Communications SARL - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ @@ -23,6 +23,11 @@ #include "private.h" #include "liblinphone_tester.h" +#ifdef MSG_STORAGE_ENABLED +#include +#endif + + static char* message_external_body_url; void text_message_received(LinphoneCore *lc, LinphoneChatRoom *room, const LinphoneAddress *from_address, const char *message) { @@ -97,7 +102,7 @@ static void text_message(void) { static void text_message_within_dialog(void) { LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); - + lp_config_set_int(pauline->lc->config,"sip","chat_use_call_dialogs",1); char* to = linphone_address_as_string(marie->identity); @@ -105,7 +110,7 @@ static void text_message_within_dialog(void) { ms_free(to); CU_ASSERT_TRUE(call(marie,pauline)); - + linphone_chat_room_send_message(chat_room,"Bla bla bla bla"); CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageReceived,1)); @@ -287,7 +292,7 @@ static void text_message_denied(void) { /*pauline doesn't want to be disturbed*/ linphone_core_disable_chat(pauline->lc,LinphoneReasonDoNotDisturb); - + linphone_chat_room_send_message2(chat_room,message,liblinphone_tester_chat_message_state_change,marie->lc); CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageNotDelivered,1)); @@ -385,6 +390,89 @@ static void is_composing_notification(void) { linphone_core_manager_destroy(pauline); } +#ifdef MSG_STORAGE_ENABLED + +/* + * Copy file "from" to file "to". + * Destination file is truncated if existing. + * Return 1 on success, 0 on error (printing an error). + */ +static int +message_tester_copy_file(const char *from, const char *to) +{ + char message[256]; + FILE *in, *out; + char buf[256]; + size_t n; + + /* Open "from" file for reading */ + in=fopen(from, "r"); + if ( in == NULL ) + { + snprintf(message, 255, "Can't open %s for reading: %s\n", + from, strerror(errno)); + fprintf(stderr, "%s", message); + return 0; + } + + /* Open "to" file for writing (will truncate existing files) */ + out=fopen(to, "w"); + if ( out == NULL ) + { + snprintf(message, 255, "Can't open %s for writing: %s\n", + to, strerror(errno)); + fprintf(stderr, "%s", message); + fclose(in); + return 0; + } + + /* Copy data from "in" to "out" */ + while ( (n=fread(buf, 1, sizeof buf, in)) > 0 ) + { + if ( ! fwrite(buf, 1, n, out) ) + { + fclose(in); + fclose(out); + return 0; + } + } + + fclose(in); + fclose(out); + + return 1; +} + +static int check_no_strange_time(void* data,int argc, char** argv,char** cNames) { + CU_ASSERT_EQUAL(argc, 0); + return 0; +} + +static void message_storage_migration() { + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + char src_db[256]; + char tmp_db[256]; + snprintf(src_db,sizeof(src_db), "%s/messages.db", liblinphone_tester_file_prefix); + snprintf(tmp_db,sizeof(tmp_db), "%s/tmp.db", liblinphone_tester_file_prefix); + + CU_ASSERT_EQUAL_FATAL(message_tester_copy_file(src_db, tmp_db), 1); + + // enable to test the performances of the migration step + //linphone_core_message_storage_set_debug(marie->lc, TRUE); + + // the messages.db has 10000 dummy messages with the very first DB scheme. + // This will test the migration procedure + linphone_core_set_chat_database_path(marie->lc, "tmp.db"); + + MSList* chatrooms = linphone_core_get_chat_rooms(marie->lc); + CU_ASSERT(ms_list_size(chatrooms) > 0); + + // check that all messages have been migrated to the UTC time storage + CU_ASSERT(sqlite3_exec(marie->lc->db, "SELECT * FROM history WHERE time != '-1';", check_no_strange_time, NULL, NULL) == SQLITE_OK ); +} + +#endif + test_t message_tests[] = { { "Text message", text_message }, { "Text message within call's dialog", text_message_within_dialog}, @@ -398,6 +486,9 @@ test_t message_tests[] = { { "Info message", info_message }, { "Info message with body", info_message_with_body }, { "IsComposing notification", is_composing_notification } +#ifdef MSG_STORAGE_ENABLED + ,{ "Database migration", message_storage_migration } +#endif }; test_suite_t message_test_suite = { diff --git a/tester/messages.db b/tester/messages.db new file mode 100644 index 0000000000000000000000000000000000000000..072aed397b2422206d0e6d819e2860e4aade95af GIT binary patch literal 1821696 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

phC$omyu@ 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 )RqK2x+&84!uxdp$d7yC*T> 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

#dVUP#}2+T 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

0T|$9-GD=%ih zJanS}tNgO{-p(z_lOH|*V>3~!_$A;ho9fe0R2C{Yr3=uBsPa80hTb9EzH$6Pt~B`etzBt!^$ZE|Z2mrwXgm%(LyOzxNoz z*1pb7aww*^66SvFlRs=cCT#*L^o%|h+F2+%fv^F80##&8i%kqubl!3=pkARC2)?0M zhWt7{o&8JlBkIH}^V~pu!xNCr@>n6c4!vb-9oB2kfNB33sLMcz&?q3nl2LVrWZBR} zsQ$N<|Nj{MfAf{fKdYR~{&e=Sq$A&|bWF*5`U{iITg2!NVk9{;<`KG#kFmlE>gC0~ zLwgpMF$8OG??}&@G;0E{BD#AF*R>=a)?b>FZmF6RQT5WaGB6^F00F(Za~lUMsp2*6 z=p7mW18gSUv;~P9V84-;zHrt-zX?1%GWl}ngEBTL`uBj5(iQX}1 zPSlhUa)A?C7e-*QW{@mf?|i__T>>Bw*)cH*!}43oz4d{^*1I~lvO#Funt&BXys-xc zk~KcJ14Du?Bu>z$ZfKC!rqIq|Lj9gEMYBkZf6MYUg?mH)SR$AqAW+L-Q@^%^%AMMY z7iqvlv+8%{znQOB{(9xk?9rt2KkAoS^PRiwIBvajvQda$ySwBD zJy2B7U50IAju|G9RH!K@Nl+T?K>Q$NTO_w9YhzfmZ=Kz!feAs_=_4;#ZP2yCl&OJb z{~)#A*^z=bM(6T9Jmq8R0?E47R;(Cb7boa_ea$Ez&dUbvsR8s^n|bYtz5)n$erb5d zHS2?a*L9@Z4I-0JJ{dXb!vUDyU4DObM8ldS#mTcY++E&cKi|2-tkChvQx~xT$2aFC z@Xj{JCR$|1Ir7f=?Qk@Pmd?s;g4zvCl@IauduOW5N}WlwVk_nhp@JTUWJi*p=^|%j zZr`m-p0~7Su&sr$I6v%M=UW)o>~sRhqbpYt_q-NfnOnz2JX2bInAadcC|4_Z8H+rc6rzO}rz zaJo3vlDwmM-B0!N9qDJk#_d)L+aG=|+SI}CY+ct$SI&;yMz8e;LB6!bM~V$_f)RJW|s+~5_H-wV*iH61BuLsLy4{m0#wPf9w3F~4=9kgupzwKgrgCU#A4cdiI8;}3B$PdyQgIR=8Ci{01I(~xMBLc_`rF*RhI(KWbl6qUkhZK(=gqJw4Px&U-?ai2^d%VnegaI8v z_y0bM{;OO0Z{^=n`2{}xA0OjppmlxcKDX>?w|^u*Bpx2v`;4`Wu$yFTc2cYz9h%zO z(?S7DPBuxmB17+kolnU8PN$G1#+u(qp`H%3Aq~W8>`#TEXf5lEpE1ac6vOIyYtEzzbMpH~eCGkGF(UI@Us3r2om~{N`L3_-8d4o3$4P85 zyv9J?2}xl|$2(ZJ*!Oc2(0UkQRJ%Tw*YEf99Z7W`PH8Te>~0(IR7ivr&KiLT3;_S$ zFL$KNJv;s6MzVFW1Q#ap+Go~cxluW;s2AZ@~DvA|Jhnr z{bIgQ`FiEueEJ%Gv~KInXhEi1w@+S>xV?YE7`EoUHGvI{H()s^c=t{e$syunsXL?X zfxtyt$pos|8#<4$hmTFwCJ%S)T{?Va7#GB7wqtJ!Wfs}d;qtn(^Dwgq@Q{F@MXlo~ zAJ``lEJLTD!cQY=b>@2dOr3R|TA?DIxEr(DSEO@Ndj+G)u%#IgT=(-GuQQ&|Aw6PL zRT(n3BJmVyBqf!HaBAq7tf%4ZI8=qNb<+IP8l3K+OSxA3K2cPW)$3^za(HyUNOr2C zPCNiB$kiY8xvK*ssQMbN*sz1H??_V|cPxPqI(7?9C{Tx=K-bR=XLm^ZnUxOxQ;Xh* zP-J9nw{9I0cd@&BdKuqX~HRwdRa#V;AxHv6_&di6cIwpkq8B11}6}K~52itjsB{lvYxctU?8|&)kb5=0an#ws`!^$>pEXuQ?s2 zV2IbO-Vy69wgdZ+zVWUnL}@VykX7UFbKdXkNa-7~kOX#i+|p?42;Ny)HlD65Rc>5Z{J7G$uvz6j4G z&EZI_U|_&7YZT?E{!15>f_eT4hvFJhx8O)Fse#DgADx4pJ1ENYyAEfR-2#5RRZPE+q_DsV$*TWz_W2 zbU^VI2h-m(H+G~jP8Co>GCb-99~D+Wowfz{I9Cf$Q9%RTF?VeXtpsp!lL}BrOSbQ7 z*eL&;(M<&YT3j?TqJ#QZcmMxPR^5mG|6i?qHv5?&1Ms^1vUOwUbIdI{#spwzY*h6a z6agkxNVZIlQfM;#-Zil^>>A$$1uz-lML8`pUNQ~Exdm({_6;PU^^PRO4<|9(Mq5rH znJl_SRf2O1%So)Mh%RLC-qQID?>Rg5{9cZrvfJLs@vJ7k5IAgg*U1Ftgms%X2}W8X zsbIJ?5rkI*wjDW>PrK!Mjf3DFDs2w4a|l{#e8fTn4T09Vj#R{Pv=aP`h@*9!PCJxW zI1g5cKi-0^o&%o+333&$nx3F-HK)_V_gq|5Xnn@k+H3#>LoZzpB3`Pw+8N{eX6j-i zxgTP|b<1dzs(xmLPE_pEQjjF^FA+wZm2&6*cV^Y;{ISZHEBmtF`As_T=Q~bU`!y7= zDxMnH8%ikx2u)+(Q>bc~iS+m>b<0w)Qc*emh?L_D?nB(9x-rM=2 zJ1GfNkg`tUHpBJ>h7LYHev0cu*6&;zyc=^Tc5bu4Xl1hwLn$O5sch<#-apzg zweF;%zp=wVaXR?m9>cNA%z0hZJefQQ-h17Psz@A98{f6r;Hp+ZlPg)YUqvhmHG{JE z!qAI0s@ai#_u=WS1SBTnM!x1^9(qKI0nw2|`oo$Rd)N;ja8%-`kN*|7zMTI)rtHrD z4`ty0zmh**`OfTzzDegl_O`7rc22P_Gt((JRn(1o+{+1>h-dJnB#mM~#LC<`>}S9m zdiT~_8E=zN(%R*oYdy2nUJoWMt2ArhU!<{9a`Xr^Y zvB%~~3hF`(V__ip%4!{D-M*vi!%7|vy>uzZgj<~*hPwlEVbM97Dk3qR zkj*vyBjx)9yc@4K2-mhQ>D0{WNeIRwrgYph5X38K<~yg9qC&hNpo2d6cS&ba<4Dm= zj*MeO*ea_MKmj!eIJ-e3A5Vt|L+EnH1%mXfEoBVkjT+$%9?U%kV1#N?N0LK- z4d20K%i_BE&bI|0A0P?>AuT;?+EqqMEj|@aDd2qwouB9|^Hkt|=O<5c94{Z$OJhX( z5(Qv_eJ=xHz0NG1fe&pQULMxzpxOb0RcSrZkR>w=$cRpUik#F`@u@KJq(Va~@T$&f zcFyedo*rs)?z?Z+A&i`cc03gStNe@fQ-S|;Y4&1P{r3D%=TnuJ(f@rh>G;1Iztp<2 zBc1hVqBEU_nY9!lfS9WML3F_#2F_(6f4{gV@RqSA z;r#CFJY#zzK~X0Ta6bxMVKq|&*h1%62^C991jc-`G!TddKtUlL_~DLZt8qc@oP6ZQ zx*&&eP|5i@2-kt@eD64L__5tJ^FW3#~MmZ zx86H>J|p*p9FPHIuDhyPUn1bqRMDOh1#k!b;!ypZSLCe?Qqd&l*ywzLy%l2VH;lJ&9~zTxn%tYSFxt3mVS$<(+3`N)jpS zqq&SHb}yB=b3oyY=N-vlA5IZxh-}G=vNTK!E=wrM!|0#YD5hVWd@v$MHd(~O1#rPC zmaL!gKKmNWQyuGPF}*r@Op%#+*t|y^pMm20^4eMPJ=I188j>m}Ro3ThHkrDfFl0)OIh{f`KK!Xh|~W|ug1mxP5w7p zc}GIjho=*6)reDkLNpJrfY}#TwA(^So^ObV?y;^i3b|1Q2OH_X}=;FeU$0M%N|1>^K*;M5A(cp-Pst z9r-f~2p1_+p!^cXi2Y6Q;g!9$3>rY`f>_3-r<4}rpeE`5j&Bu_ph^^d%0nvhR?U%* zsw{SL!8}ZUR~syE&c###-FJ#90{xu3f3H+oy&t3bQmiHAe=5UNd05;WrT|gK|L3#n zo%yYm&Fr6L^RM>VZC%opLi0?@VT=L=@hT}E)Tu1Y*I3B%aRlkW(gohnW!>D)!KumT zAUci*chJ$-=PB>E0OOp=bN{Aa!{ttDngTi!5ugHjja*k@L3#k z=O2dTK>vRq$Gu)z;x+$zWvQ}KX;hxAyj1y8<$Ee$seFH>SNY+}k5zuM^3#<+RrxL6`}6nZx8)zszcv3v{$T#e{PFx# z`DgR_{B*vSZ{*MA-;sY;{=ND4<=goWJ!y(uYSI|Sbe&>UOiiV zp}JN5?&|NU{=RCb`h(RUt$w}wKUM$N>YuLux$6I2{cF`(`kU4Nulny_ zZM(L+e=q-d`%32c+uCpE^%L#uc)hRv9$xQn-^A;|_6K-+^}#kcY<{?X zAFmI!>B*2k+@9g}kv95l`Ai#@cK*rsxAQvN{v5AI+6%ls+Fs`Mv38x;qwP&zA8$X; z>#vFfs>zVE}uPa@RXSMqlUe~%BPrZ9Ruj}0#d3~mPE3b|2xA5BRYGyXN8qa1|<2l=X zh}U!7BfLJ_Jq+Pkoy*9#YJ=Jf|IXrKIn3mVA}UeN0N&;`xL4_~;S*FShcv+*Mr zG?G7b;TW$!dO;)k!xuD?AG@HD{P=~_y#A33T9===&|vDp|8JE3|L@B4%J*hJ$rq)M z)>PLCrzgZl5xajJ4kBo(7>UCy2q#s72`iG;3zv}4N;QP`e&wgTQ@Vtvwi2{0BGJ(f zqpPB0*$aUWqD}@2(Cvf zc*R;`{8@ZZqwa1{ehH<>iVtn|-P)E}H&U!EEwUnpe!*SqjonMM%2Nr)DW2MR!g@9- z%wM9tW5afsR2K9dMq{VVvFGPt3a=K2Lw*ufsBofm{x8YCo>iyv@2>nHpS+GgTI*eP z79pzq=;RF&b>WY1)Ypv~$z0tAt9x;ILsUMvGEJ&-N#=6;S128F6kmGMG}e`^x{Vw- z@G41PM?$GL)Ne#0Ys#uGKj*(su?w{?mgBNbGzd4fGz~g`Hehd{-|ATg4gx=L5;xd# zogY&ZGbB0s2=`+5P3}-6;K-3f5uv6jM2vPRGAz`GHG`@+ON=dA8>C<%@k%W#E`u1H zb`AhD!%uur4T%@aw~P*D`8lBu1ibvk?iM6z{fTXO|8TQx zbl;$Pp58h%d2dCoIaLeckHfZTuGi1VNvAuA&%isI&t>D;L}aTM7V=mngXZ-MODJC_3xNYUIMU{`TyXU;f zLRdJ*Kp#@g-dryY3F8I??Nj}Y?wi>ihf~TQBi3!SkQ%Pm(3?V1y3<2JTbFg!pySxo z*5~%P6-xAcSJa_}c+{MpVB_h}7wkR7f&*c8rEF=}Br#{IADL49>1e0YsVMUsp@%pO z3}1){(t-QzD->hxh>GVQIjn*5nC}05SvRY;^8Zlzt;+lP^!4&V;Bk%ZjRa3AVv%?2 zCKacNq)ego{=x3mb`7VrIf!i3q?Ce!jNSACQADBRucBLEpv6r~OBbJmvo;7Zwx+t) zm1BA)xI*_7ggdNC zxws;9-;hX)3u#~Ix|UiAj!!g&FjlBq)~7THfSL6FE7kvgUsi3P0DQ9YVD^3A?Bm}$ z(7oR7=~hAmma+Hrh`iZoHjsII#Iu%@v!yguJ!Yn+Dc${Mefy! zb-GjluGpO$i?2^A*SlH=BB!5yea$^nQ4k?QOSWPWDoVw-HwfOjs;+SamwgSBME(sW z1Q&MC}UeZ;|kl7S<==j+>VdRH%%S{TdWaNle>g4T&ealW-B}0>E z9U2F2eZL^#&E0EthwQ=QISAg)t=(8vdr5ccLJXg45GGO_dLHv+Y#jU`$G3LnJf$Jw zfejcptv7Vl1LW{j0)aGgtsN=0a~8PNZxuwZNB9S_gs!j~9Ajg%yif}M2?2aE!{L8_ zCaZoP{C%zRi*x||Gv90ru&eh6Pf@3j!znDA$aK!oCLPL}Bpx)GwX;xF&jJIeehZh3 zoC|x;#CmGvgN?dc-1vA2tZqo{WnFduIF{0Ceys7@l}%SE3z1GB9$ejZtv^y^JfoM2 zk1M?5B#Nsmpe1P;^WyA43gEW!j(5GQMjf+LcTaY^SN^Ko~-;b9f1BaU+(;9?e9uSokw~5K0bMfV^=#tW4bs92&ur> z0}G(89<$R4q>wT5G@=F4j;@a7F{l*ZN7XNM)z)J+MfNW~gq@Qxr2JQYdr>X=KCN1M z98OXGi@8I^viBWvHBb+{oWk2n^N(%q&-}UWP4@H&C6w_qH{!sns&|L==dk0<2aM@J z_q{GmOhBk3kA6hxu(rR!#L@0v5F%6s$o=GWJ{1*tH%Zq&Upb(f*4WJw{z5K-`oAz@ zcmIDjtG<-~C;I<<7YHD^!dK@5Pe-!mGbyY}w&7?3>~2H&6Q?%~Nyh`EYDm(rfh+!w z?g7>YJ%)rztmv({Cl$8h*eJn*Fzye`X7_d-n3Qg15g+CR#1$6>)9wlH&yqjtC_{0} zf%5y(9wSJ0w4U$Yru{m-l@NYL_G{cf7Mv9%DlqSkA*=o>gSsF!#gtLCsaS9~D(K>3(N=P0>iqlL%ANm%s{h-r{3{rM z|1LYYtJB}Qt1B6Fj@5mWFWu-VxAf}8wBT`JE})AH9vf~o1gvl;t#qVd4ybm4o$sCG z^WD32-|UWrGqXD1}INW_e*5PN1UF;=Tj(;ezrM?-#pwYTr#KXdR4o zQ)ZCwf_^A-F_b(!EYA9^z`@*>S@O5Q3ldo~~~Za_!_bix?boZ93nI5&}X8 zKw)LXN?@(5=%%z}mViPC%p#*}T{Ry$ zJe^Rg8G(?GYO#ZUsm(RtAg6Gujzku%tVus^Qlf52LN;Ycze4LQYy~V3j2dr&9RB|U zS^n4aJ1SqTyyNxN0sLI|9?jTP!a<2_(?dh->diGuF{HsgzfxZ~Q<@j!MTETQvt6|b zIhK-_8hb<^k}~H)eMLa`G5zLYK35GWa~>$>U@f<=AfwAPGP_W_$Gg3%o(vRl- zj^ZbKHkSF|#T_kowx3bl`at(1vK&*d+&X#s;?d6#zuIN=jMR`o!u9D5uTDv#4qw2R z6)^Rnr&vxw%qk!mXnB}v6lH&h=rX_i{{1BRBv`>JE!O7Y1>9>rvqg*!-$WXImDYTo@J_V?xqkY)MCD=`w8B2cDlZo4RJ&nHpOL#Ou=GbL~R*au7TpHao9^N z#(~WxUx2qCM7qv()$<}rIU#zyM}(&XJgTtTyt5>6!>xK#_v11x)2#%GUG%+&X*S5? zDaYkXP`!Ae)aqRq&_Rsq8ecMPK=@Q9H%co88wdQtk8^9OdoSZWHua?h?Yyz}_Zz|0 zkoGLc+x!4I1UL?3^&R)FGO2fQu2w($h-ENOMy>#PoYi$DO(g&*>B*s)#uY8&e_zU~ zzpHw6{(DgX|BmcGW?$O_PEu3+Iq)qL9?jx`%n= zy?4KzjMWc~K@>md=br68sAr!}5T1*8I1iV!zwvG5BAO&WjV6X3Uczr726$WdkUe}t z&T{Mt8<#y%E??<+lJWQ)glKA0@A$EWMIm{CxJyW0RJv+e@+x4s^ntVM(EC551V~DQ zfEXJlr_nuVV@SwTjGdb&-Gy9RQXWnL10CCoTLE^KYSi3TXmsz_R!DIe@IE5z<1 z{QrS~jEn#8%&NaP|Fz0Lu6!W-(Tw_ru^;Q*BaCx)Dgok*yH`h1dl%c0W6B)4y-}PR zPzrAFpiHWF6cl~A*jMB1@6_VOJfi{v>&v$KH8OtJ8pRbt{lBP8NiMX zbc!ww+A_D?>AJCLRlX-HR_-e+6bx?Yh=k?P(6zGy^x5oM+m)%UdnbE9ai9G$bj|RA z7yupURE78Iu;+^$1y|(CQ?C_z3W(UQlg#@T+6R)6*Yv<`ADm)xLAZWF%B6Kj_Yp+` z)2%&i)yE{qX=z3^;W_6hzW2CtO05rRnH7bT(vTCVAW>+oU9qXbDh&c`v_c7~ATJIY zyF+zhs2X4CK5SMX!SqC|z$Bvs1~xz-b^b5Q-jP+$=KnCitJ1E#H~TVQW*gn(I^@#{ zpx&4b_GZE1ky`0!`r~tmE9-w_$lHL5ntdqOLCfPP+1Ta$gk57 z6U)oay^Vgr>%$5W)5-D8@H6dB+6RKz-0muG_exgWG#(|w$` zJvNnq7LC1wPT&uizKuEJ+P$7`zCT9sp$wNg-O_}>r~w`c@ze*pM>$t?P)HF;8m(-6 z;xy%{5)yzY?_#*8DXq5fmg6B8Mrf)ISqT~4tDrRQ?m{~;HY7sNx+JT)P8Z8qU z7{+WV>+iCMEw%f>Vh5I+BgPcA`K4WTeL1`b!P%WThoT)FR%|WWNca)-|19wTF3s-B zs$a|hMLu8oYW53!G2x?iZP&>qr!>Hhc&&+9&gSO&x@bANa!Smz;<>@8C8ZV8@1N-U zh6M@v_lO1c)R(#WIdfKtY>|LcqdklNOH81hG&^^NIUshR_-L%v2WeoVaakx@=!{TBeGjx zex!9-_fxjl&rJ3`qEF*VSU~Ki(ujG$YB!d#3yQoAQ{!UQ4-qFV&MX(}KtsSnDX z?w(-AW~S2s%+Y!C2Ww3*K+r$!yV6^sJ|_y03pyPB_p4d;T>kGW|DZCT{py6n|6k3I z(H;4`U7$}*-gjd!&<6(UaL|XT*}@qu7hPd5iZhH0$_(G(Z_5|DpR?`%KAJ)K7>Z7u7##Ah#?}9B&h((H;8AAF=0ro6>73StdDGYd#LsRH`8A>?)-mi z2K(<&ka;U!XU*Y;hcuknL zhY+`J>8cCC;ptaCICpcvFupA<(R zyasPQSn}Ju>Q8`AnSj_tHX9SLQ-W5{52LG(W0cF<>|B~UMgJF?bLVw72%5>R4G);4 zZ^!{2>#Etn>{RRAQk^ss`hRi5$-%QmjnO*vW)(J8~H!Z z@2vbB7T_=NW&fjfV^_M+V5kXHvG{IzgjmVR!}xA%%%A3~fJPZpRL4)?U7@;j(Gpr4 zH2pnr9M+0dGPtmHZ3{PbPw{NGZr)S^ku!b-Lvtpn6bXIrBZstHAAakB(DO^(HNEvz zLX;8PccI6YvCK{{uhKnmNnkfesjhzUoa*ubFy9n$`uJk4t3C{eljJ=|V{wNFM>ka3 zTJ@U*WLK?q61fR={u|ZDPR@O-a)EZR%L3P)BSTteG z!XWDFZaVsKmv0G^jp~c2ys@jE4YP?v^oX+3$sQ`i; z)Va5=={9taO{LUC#_zF_o*JiJR!C;LAK&dn`^rPZeF+BqxvpA398ReijV{u8xZE z??R^Aa2tMFTDWgjtz)QP0IYbDmHl(v8>^9418}x(|7GQuk zbI4#W@s}`^ zp+O?l5Gs`2`9CZDkH3*$RXLsg+YE)&*ZSk-o}{>w>Yn0z*YtE?^V{wum>2~d6pk@Q z58N|geXG5SHfsurDU!idyD~D?6uI~lOc%l8JnQ`mx&Sx#GByJN>dhB@JRaGENT>VG zNlZA;7tlx2-pdH!2wTL57sW)>7=k6?8l$;F<%t1Q>DN$yqAPW4D8k1l4>Y=K!)0V} zO4G>erV=QK<(vw$6#bD-W%1^%TZNuX#Y;4+g++t!YMw-37>U?ECq#cytX6B@HZ;|W z2ZR6h&hATkf~lnRbhL*P(&;dD@bk%CkeOgAoz8sdgsl0shgenfV`Vh-B@~l6`Txb? z0sT{S+b3hap{uqKho=(^cjUg02s-W%31c|g3oxw<%Psv6P-c$?x%1!5s-MljRQdOn z)$F&j=4*WV&-bS6=p|%cBF}%6vIfyP7s^(Sz3;rx;^PXd1}N~i_wzkT>>f@L2#6f} z#~VZdXfT5909md|l!iIB^pp1Uko&8SAcJH}Yq}??-P!4c$RKX_juiH#G-<{+y)LZ0 zWFHkDILxGVX>T8cnVCvZ0*jvW-Sp}#js%h(tlvO*IMKVrnVh|(DC2d=qjh@JTw{67 z!3vADzJ{dEu=&hj4WPg*uYJAtgS5SdmF7EXCD5q4m^0)_X^o^*i0VfRGgJ>fHdvR6XmpRSQolOw@p zHiJhE*w{RMkEm8&g%Hi3f}~jB;CX!8LIR=N#aSjw8E~0adQR3ifhZUcx*x~et}{jC zB(3>o>Fsdou(2UwdP~o#_@-#bMNZr<`WI^1yzYjn6M$wTwj=1$Pf!_=kn+ahDr)6D z>FUo+Cuopz8H{H6xK>|=Nx7tMQC?y2?b3{yURCk_- z#%*dVAvYa~_YQGjw53MWjLGPh35~0(Ix~f~+M0dOK<5U=2bRO@dU6{SB^+OCu}TXZ zU|aoqzuSPt*BH;@!~oJFjP~IW=dbnNq60Uzb;o4;5Z}8;)%R@<|Itf7o5yPm!h1Pz zbI5k$*{JVVhjAsFVr`4LO6QC&nD&3WjTVrYC`9JpTmuBSyfSWIr8c9+ejwm#a+(%T(x5q`G7Ol4JUs_C7rtAQWw!A9@)4+Q4gT-Q2rU4>Q%e zVe<0DA112(XOlcs12JccfNo2^e?N}dCi8mh^Ir-dQjySqHwi2QIlz8podTaEqvAvQ| zJ00E9u|kC6o8ln~^Yo6=zTVYx$)+xzsx~%e@>iugZ>i@0z|cSnO$Bf6NxK+#`r;uJ zofP9^!BC`)2PV_sJ(wPDocY=ujAEo#?;U(9RrNJ%-YH&%i!ot`lx>e`4hBrUbxZGU z8t7Dt7ZSM|533KMfW}j_&0)~8;FcjbyRYxa+brt`%!6q!S(z00A0V=^GuG>kpq#0Sr0#+vN4gNfKbs@eL|b#1Zr>*zt^@p#DDD z?7h=0ZAvqc7=m~>EVn2_s%FB#xD7Hix4_atj(e>q+2on&1TI z7c6Ztr})y}+WYO@FP!kE&&QqVIqlyBxFsG= zDe1z+h<9inWa%jZOTZpHUb1yl@7)+FRDj+!+5e9_geROQjYSb}ZNG@3z^wrSG>!)5 zM+{|45#mtriQT;Wl{Db*peUSiH?iutt^R8cW*MegMUDJ1|71yKOmP#HLaP|Bpy#D% zNWZEpM!u$5^F=i*({~CSx>34`dh^me8AT-MRXhCe#;kfn{uR#u$?VVW@c-Y{yT#0X zituj)Q`y-zcXYbjO^RJa#sL-OSl^qzZ>Y2?-UE&wkLyBj{}c zTc$KRiP86~rCOKwB;I>?50oVz3F9=W&9_4#NZ!k7i1yAc4oH%s<~rWJ+UIWiRUsyMjgX@{&1vkSy-HSqXm}V~)X_JPRup-lASo)z;HW;@Nu!&^ z(q4u}GB`X?HV`2VyTP6RPiNKD{4eF7tNd*CSF=yQIu1aicfhXPgkUx5$^>SKG{FM4+# zSAP$kjzP8Z{IZuiGZ9r6V#GC8i>tv&9*9ey=}BLAc4|*+5vT-As03n5b0JC70p$1Y z-UoD1O|`DM=(!lHIDD9B?4-~04_inW_k#JK*0UXbO zbfzrSMj7`IOp5fNMTv@cRGQgv^zqW9k)RxCVCnqt%YG@VzL5XT{9*3@UwU;;|2Oqt zdAVo$x+%4t2iF$QpF}C@%u4P4=JNUhxaaFQ(zOG~w0vTs0)AnoMa?Vh#g(2Ec4wxx z5;9-WrT&JoqJ0jzvB~an`r(b*Gn>?pdq10-1=q54_LNGMFsQdfZ|dElYkLpmzX++t zAA3t5SVjkQPQJK^_r+}us?LxCt*v!K??cYXNw5X+aN|TGYTb9rYf$s7U0H1flVGho zi-QDvM|&&ujcR2*)5lM#9f@9%Php8H9S&i_(Y z{Yd`5RsM1%&z4?`liPYnPm;Dkw{M(mnj?_WlWUMIuyePOEK_)-pb5{2#?3YKnJ8UV zuqB^}q6okqt(?B5vXG{`YX=?$X**kAHb~h9yu7hQfM+QXk>p5xVa4^w<~sL@Mz+c# zoD?5LTb?AIf0U|n;7rZ zd`dUZwmqEUu*oj!?*WPpBrZ*&12zswI-exdRiJb-&rY(VRp-hIpg z>EeBp7a(fBj2t38kzToXOdrs!}zCBFONiJky>pt?Yk{JQBlx+besQ72 zt%cMP+~He$4{|6F?nq#tjNN9lm>64SL-(D}BjDJZM_58Q9u8;bL})umJf6ertiS0{ zyKW?fEdYKhufcDDmwOTdKAh49H*$Uxd*BvFfU%}%(oa{tC&}MEwgnuKQYKxr^;i)nF&C3|NP&V z|Iz$il|NRw_rKorztWRP@Zpr?X~d5ol{zg3vdrXgYzA8+9^{s>*jK zf>eG_2_5+NJky)8eVgLn$MVuU=(NRmgHDSt5$LpU>OI2S9ZRWK7;D9bNr46Utf=tl zl?RIB0qum16gb;g^dycuo6_qwZs2`>AW$lo*Or8|`!Wu^ZBcD3ktQmQqt6sa1ji7N zGjJxqt@n`gei9UDB2mauOm#w29|8l|qQDCb5cf=B?ef|h{@J~FMQY`uSXlRzCf9-i zXiJbn<-C>*p%_)A`k#H-*RuQ{ALO*zQ%;A+BI77eN(Qu&FV zm$W@O*>{Mn<3q;n!zuHO8ePV|5sdOTU{-Rbac@o6LSfIqfwiDljIOfEjOc=7uDrDr^r(L8w&2Iy7C_X*0sGOcKyDRmhc?Sw0A3sE<-=)xB*5I z@pxaaUE*YneS<2BMPuhFWQV;)2F@7Ks%+Hx94a=V?1Qd-i<@)k9J9pmga$if2QD%; ziRt)UGH+-j6Qz0#fo~MIjg$vqx)=CAmuAPa>fQN|=Tnu>XMc+?_Vz(xg9KlXrL=z? zduu%2s6(bE=r-ATm+DXz=z{CFA_mxj6F}Am>)pK{;S1dr0Sl}2p0rUUl(CK-#ZHij zE#RpDw?lpb7B3D0n*zui(sS?YolqPwl~MsVb`+t|NNjEl%&G`wUIClW7A3}YPUK`I zi$!%)8#o6Bu>)(*o}Pnvj}Bycq+rqgtMy&I-9SAi3)qm+7tC_LmuuOGmFwnsGW=&+kx*_Vo=?;|0F~zOC3;g ztZu3HxdhSII1MA34F!5AS4#~4D>yJhG6x3R`TzB-`aSud$ZxK6sfw@f?f8%V>(F#udiu+xTMITdSZk{sV zVlQ*^h+{XzU7-+2JFs&RgEcfabQtfOtD`-sykl4s%)wa0YNwDi1pV^raJ`;0A5HhV zteEZ`yOsB^s|ASgNy{@CLk)-A8t=(TL;C^C%K~F(O$f;vaLdklCbD}d>H&+B3}8ek z;V-Oa@1)If0$dyoCuf2hvyI%rgVz7I_^#G;C{NJBdzXScB4^}>z{rT+vu_t)w zhpkFanz@Ik6Xb9rn-r3dl#?u9sz^x0nmPO=B|5yc60q>8dU5Jh6vN#RaMen0E_D!* zyWyxO-zdfG=FR*lh2|>^hzj3lmxpgq>3l;J3z*;MdSBF~kVF)VS`ci%YUg~h2jv9N zzKndxc2qJ)TG;$&U%5&%yDtiV!}Ug)omxSQTI9>e`4e9*9ubBf=~a)$8Khz**hiElaf!0 zn4X;qPp3CmE&eVp8ys2H>IQH4+MbE{PPZ_=g2B9fM)s?1#LO6yVLPvrm)P|$&e zCJ9M@pyHn9CL^H`$j|TZnHcYM>(iTNEP z_>f;KJO9~AmVY^ad!?2ACq8}6KHzq*=&Veq&>u&$Q-;gwEn_S%zafyonCNAmz~;(I z>EX@i4+uU4Ea}1?PLW`U#Jn`UI1@y`#_5L_7l3^IK&GyBWzUJhCdfgJy`|WOE+>p% z>YPq11Tf%Hlp0-|D=OOI5W5aUDz6t;tkxl2L2w5+! zh-&uxc>30A?=+Y8?9`VMG92+EF_y-=3abcK*rwIUhwg9`3&`sVMDrS?c_V0v^no8* zUmy_2!3TTJa8gOlO^QNo%mkmJST$()J~ZLd!Sg8<=l{P?`hWj2uUEd1{ap6G zod$pmrqol@GMG_AIU$4TfnJQZ8W#a((1nie>XcuKPoVTA*iZoauI)YJ{JaE6VeEE& zz>(1`=}VdOP0`YL=#s*|SjVYq>_Hf~#% z>0umf?NvwF!~HWpxQl1|MH!s@X=7a6FplVGXHeNkB+?K*jkWOrr^x zcXwazNvQqU^wG()F?OkTg0glVnw#^=cfnUGj)1UWP?PXd&-B`-pPD>^h{=D7dTx*Z zYBuL-Wh9ikFZx3bNBP8oN;3r^&Zibtg6*@@3GGti7H(qFurDoRRJp=*n4Rx0h&%r? zS#_TJp9ksw{ZF%*o##K{JFV+`&*~T^d8A|4c7mDkDhCcklSXy}dTrlhJ_&$p`GAjz z<}0CtfDyd4cTQU`rLb)5@Gh!QuskfZOG5os>;9hP)1l9OX7YxN_{!L=P;AHfhw~}a zU3mg$O36JM7g}}m!oBBA)W-d$l6_48EEVz9x!?j54@sg$Tqg?vq)kMJj|0VLfOcF+ z%U636JfBIyfTF<16PDAEwW4bw_TRhsC1Y$7X~khc>x0S^E`qoAq;`HR#l0NA!b1{9 zLIN$Jj_=}dv~OA7|1&z{TM&+alRouO%}1g1q>P*uD2|le{RnL z9n$&#=zpHcKVA7+_S>(y`e&BC(3Ae@v6PngW0&b+IkRkIy02)b4mG|nt_Msr@azzs zzNcpr<5NlX%rO$pK@*_ZKaU8s`eZ677FOugGtjQW*ITXO!{Ob{aeY(ry+YGZ?P?Te zh|45iRURUBnf)yK!QS)QGE=PtHbOkQkI~DSvj%f!(3_R<2d{KTDI!0J@D>7CUwR03 z7D0rMZMQD(J;y_UhbBZeQL}JT7K>^ESD*;OWsS#`RS(Ts!9Pux$2=rij=qSG{SDp_ zjB-UE&W}^p#bfvlA_3Pt!2BlfJZvlrj~o=|Gg~1n(5DYu?&eYRN+t3ocwKG)&X;LI z4NZtjZ30vJs-6VWXQ$6kw)F8ueZs0;%&T+tF7s@xGxzY+Y_ii(en_+`Wlj*_JrjD! z3=InWFBERM`~SPM>Sq3H=>LDEa((vQ3Ag>L{wu93FI07hOeIWCG{7*>sHCc&!i_E- zLn&AGiLuXI$Zbe_A^4rJnmXH{zGyT*4X7=y18Q0tFesJNqla4);$2$T}8QjE@; zItg(qmR87MP%`B(J)&DxRA@t6phpHrz`Luxj*3CA3g-*5MR{N(-2>nG|7Y)Az$CrSyUwqwG*Y!>*}BV;EU9J59?2Rh z*3Ggd%hG7nlG-gx8qLVE@U^GA`l9LXYV~C{<5)ueuLzaXPAV7eyzyEvAsrtU^I#p+;N3wPw`tehq?&;BYzH{F5UjOg^ zZCvn&-Z05Zpj3+r`)*E+%^F%6s?-I}5(NPFqpKbf4i%RILF?Uqk97Yp$i6A7tW`?o z$-j$FWxh zgX1c9SUsE?;6@U9pE46$_S#}GOc3#i$S!?%;$=;-pHEj4uz^vj2W06~7*R~Xy(;hE z(nRJ74n_`!`^a4YMK0V$FHnqG@fSGCx?ChJX6v(Kb-7RH}GFl%se8?-{tlq$b>p# z{E?5jgtD6&RCpHG0RP25Yqj}4JLvnzA9O@@4{e;xZ>NTU^h>zqrsGCWNc_ZYnWhto z?8CUR9aPCC11V>rD$qs^NO8dx0U{ytEdSQB|L0{dXO&CKufhKLa?J3bv!6Gz=9Sv# zq~6@&MZnaWTW;%x_Q%QgmgucY(+FB8zCJttup@rVyTQ#d1ncRl^I-`b!Q2->0`PGD7-QYqh0|KA1Kcb>DmzK8$NN>c=^Nxgg#ZTx6&E{~L>gqEt%(zdG( z+&>AJluAgid*+trwJub=dHktI9=IFFGtlV~mO+h}?2LlN0lHdu@dOght&@fV+!3#? z8|O8zVP6iXP{xe(j}9UszGls3MYvuhz&TCy7>0Mh2$xi09p=jiZiB{?P4TT$?Mc9W zj$FvmrJ5zxHH`$%OuIr@y}(3bWhr~%^;x7Ql@ivFpOfr21C;Z7ii?LW_7T9#&8ykc z>FQq6JOFRmuU>woc7o|tjNQpwAd;hyFosUXeNB<7&r}l(&rfe32pmK?`@b*yi&^Dx`G?^D-{z11-}q{LvN`E;0aN2|?1(KiMt{Os8Ls(djoYqi z;3c`!lQq)Lr@5)p!)tZnY)Mo2Urk9Y<|U4-B4cX8ExxUJqt;$+>>t1OxQ%&2^C!^o zTjWiw{Gf)_je4C=E;Fbo8+_vINPhjZfNXb7&do0tNDejgkVUwvDJESygA$;I5!Dz1 zWP@wrC3w^g)D^2W(?SQ#e=)no?Kl5Jc1wf53m4ac_rfK*w0XT5i8sgZVq{@Qpnmfu zz0gi;H5#giBFbvt-$S?vFoT&tZfEXr62LzL9Gz3JG|8wqn`BEIF5_*Pi*Y2*3v zR92zjVmNvHFRI?~O!8|3DCvRLFHxnb?<;3eKp<)ah)c@I|KBJ7|Jm|s_P=0t9D)hk|m?T(S7r2j$3$7qPMZj3KIg?f3 z5+_je78ulkFUb#4^P=dTxPA4>MNLNF{&Pgg6|EuzuO&{_kfJ`&yh#Exkup{;uG*ym zl&2!N$2-y*tcOw}Bzy@ku`_Avybtt0oE=$lwePZh70!}{?9kdKVSZI>5c6F z5B#5vk2H-5Hfd5!LauPZMt^bAp9>;aG; zXx`?O;mmjw#$_Ddnts&EW-9U}5A{>1xvE)QHc(G#a|Dja5%KmbH(z3VMGcC5+o7H- z;-(<$H#UtKzS>9_x)iy>(`zsuEN|5Sa;b^g8hy;gQewesS$Of-<=- zi>jzi|^FsNFvdS?#>X7hGVc>_#{ccMV`TW z%cQJijHP&ce?jI2UmDnVLC8?}q)`K+C~MxqDIQLl;WjcB9@f5TESoC)S!p1D7xB^| z!}KZ5o&8Nm1pZb+t~?^>PdZD(^^1Uh&>C*?P@h?bl|4KO5=$#|7E(LdH(Sz5TDe2i z=uapZZW6P%Y@dy_maqUD=YlJN*{q!{v@Wt7RKT#ww+~PDSG+Ar zcePN^_Yed4tMzJAY~s@?Y~rI#bRfS7LGCKu|5l=*$GPB8QuC9$9v}s`HpL`PrtQY@ zF2YD@O`%$4kQojz4yDOqF_CXBG#9uaOgQvhE}z%fY>EW@a7u-6!XZuNVv}1V_F2NT`OOCudb^$q=02=g_mAUO=_PQ{T=^NZ zQfhUWZ0jq9O#%Z*>MtDVg-x-3&!m*N;{gKk*AOjhJ@d0G8Ym%{qG`@ucEc#%0+osE z|M}TH%Ky!Q{yCEU4Bw3YYTVp>!1QMdfOh0LCvq=XaYSqYHUv?9WVora8OZ9Lk8doy zl>R3tt*p_@Y@TR}YjtKKf#@|J<{sgx=w$G{3*c|mZQ$7Fi}pp3YKlm~9oX0Wq#baA zd5J$c;uvd0rd~f++s$1J>pbfDc8yB&eiky7!iO4l3C0nE^*S%|%TRX~DRz(hd8sK@ za%LeUWPG9;R040#Lf^L-84?p5xT2`(TgMSUSoY+O}^vL08y{?DPp~l)DkWBuHHD%YfPDR!I1IEDMktS9*{KSiiinWzYdU?>_C4!}9L9`4}m|GN0U|4I3Q(vOxd z%hu2F^)LMA8aFgOVd~Z6Up$hU8)6FPYAdy48$zVo+`y9>y0|&JptRB~T^`VGvP-B$ zg@{b7TiGn^2>Ef$RY3gR)tqwIB>@Q*m&w7b(HwaMqvz8~yyo)kMkcm_wSn|5wG7RX zbb_oBwq8K`ys&60iv6Vidh;=priaFFYQ)kX-QGCnY5%w9=amXEKEZ)UYKm#tr|X-C zq}7uc10xT8fmxcA`TRT!&tqpm$Y8!KN?NO{aY58aa{RVcq*Zy7&=zQW{)(yVj&1RN z$G)b~icci?VexZ(ynoB)NSM||0Oni-u^*!}X zW2QMHiK{jeI?5utLs@!k@X+CqA&H|Kx~mzCeRWxKCBcl=lApL*nQ(@NYH^Er-d@Nxd=7A4?X79i<+V?pROk0wxeG7W1u3r-J7KEWLW%w1w_a>Yp&WN z^sSaO3(U5-ocxffE1OR^(M|BdBKCf;MtlsuVa5PGsJaaz?YdfAFR=TGAt-DRR$*aO zM@Rt_>_78AUo8I^{9oT)y7gSm|7?7=X(Xu=jpO5mEP4b->q^SVRZk#5wG^ywuTQx0 zNBWD5K9AGMji(}_VxkP#+2yTmvTz)q`xp6rQqq`qGOxo=HI0(I`o#Em8R0_n7F$+f zR(`Em%Ki(2ptT+ya&0(ZB$4 z6od-F%CF8-3m;rCZCc)@Y&6A0K9e#SA->!QqAqvCP-p_gMwc&CY`r z@$lOs#fd9l7)y)?4HPzkz_DN1BTMF3#~artiOlfQZc5J94VK+Sdu64pz}=AFHcSD< z5HHux{-4MyE9I9M`OJ^o%qws`<)4OnlWpCKW!H!^u> z1_4~MdZ7hO_BMWgd&~1VNTBTP4~UHw$RbljaAH6T6r2E^AaPnKR9w<@6y~pwfAg3R z;6af-VhWLn%>c6te;TGoBQ7(aOO0j9b`_2TCkZFiyMiwMzbmUOl>bxtOQjA7z`I6j zfW}KrF?dfUO}33V;ejY#gn3+1TeErP>LYROCeE{#t-;-TfMejw@IUQ4@I9JOfvoeF z9zrKnoq=BMqR(Q!PFqLRP{w%^P`Q-`A2BM+BjkLIHRw}TMCb!_-e`&xd@6-4Dk7`T z;JAn`W{w4gaV!?_s|qadQmnW6^P!l4D|=69NBLy)cQ~z-5GF)~X6QsS$EIuI>!~#4 z283)rl`l0xtGuNjb4bXz>-61Ed}&))vzEpEg|%D%aS!DF!RCu{T#_b4#v|~8LAC>4 z-az4OY_A=6%`-i+8!|g&%~d6W15x_Eur^W`Y^^Wq9&diZ!h-~tD(ai=8l;q0-wc6( zt~CT)S2bT?C&&O)60UxXbLOZ8Q?ebW; zawvti$!)?twb918yQ>hl2T$9{nzOn(6FUhVmXWLDhpn5vJ_aL(qujPIUyUWuX4S;4 zD`A_Iih>?M9C-a+0m%M^dGY@alKC2aAr8K6FNNkJeCC*74mv@N+0L#%>d9Eg9d?G9 z$GVCk<-~oQU4c`Q70ln6*I(An7U*5*r~aBLu#@BEE~2bQWlUfa1FhmgLA(lp!rEGG z_4L4k#5d#p&DWb>m2^xb6jY-3?uaKf*QpFg1{I#qy(jhV?>!sxl|63A5t=Gj0_Uq; zu!I4e`4iG^^dO+GHAMhElY%aZtH`I6Ay(i`n(7O`#}LFWO1GS|AWl` zsC=lrR{AOC|NX(eT>o?TZyVP)MFmT?@5=GA8!^7mxn@<{8EU6AbXn31;SxXw%S- zZWg3XwLUOHJ|+LLre_9Eu)C2h8`9TlZU9*#ZJ-X-v2&5-#fi~iAGLE?(N|fFUXqA< zzl~DP@1#t2kNe?xQ{>=N)qBS8V?=iz>@)LYA?DEEg}uQHQ;hTFip{3Dz>|pNBd7MP zn{VVgfC5_C`oRiMRUcZf0PEua<*fW$<)=%3pma;NeC}>=;lI?lzq#seOG4ZidBLgF z=;y4Mt#WceY6&+f)N0q8qAj1E_|$kghzb+W7bi%(o3F8*WV&4;d{g7(H4NJ--$42X z)=Ek~=*gXObD6cmPnh5yM$h#Ld3WquN)Kj^1d_4M+k`4IOvc52z=lGas}yUK(Ew7cuR={N^#Ln7Bw=+5K1LE{O`W(i&^E1bpLY^ujG{qeWJjNf+@VKDcW+n8xqt?+!dC*3oroaR5&p+ZUV68T#7ii z89Z?$xq#d2)%71TGsDfx>M(72;q`7XYFoC5pdSxE9xqE){k0-CSQkARsIY*4E zV8u5f=dFXixRGef5K%O)Y}U1r)f6`@x{26 zWd6=-0YHO7GOpA-#a2ei5!AAZYB@nO@oA;WH^?oZ_-IwMO`6E(>NC3v6*GES!u3$iE{m2sp@Jz}K3aCQ=Dr@ko&h z@JvvpmMjmL3y1&BKc1pCyX>P5=l_34RykUJqx7#z&%Y<;e>Xne6mcsm{h9G@WK^XO z+;qc?Op?yF20!(8$9u3=AaMo3!~L8+W|O$Qlhp%O%M-iv2yw8g1`Oqb(I3~cM=^d$ zQ~a(oDNOY7q|OT(CG8yG-J}6)WZlFvYgAr1^>Dmd>8>LSCF*AYRDouL}va{QS4zoUO>T+=#_Jp$B{z$z2h zxP{t~#e~4eT)x~F-L1w3Jk8apSM79>0VZS=?g|$L_dFm+?`&1v;u1Q_;urXFyy|`@ ziA4#B(P;0NZ~)zOIRB%tuHs-mW3-d7O6dW(V6DEb7bbyUwk zMU~sBN%Lx~qEgr?5lHvINjn#{GM(+j&hhbzFfMpUp*}pRQ6+@f8}$Wk1}~2%sqN7p zJ!C-J!B8&cR%TDsmthd9or06>1T&Z@-W}jO!gKjyuI#t!cD0CKUg^F$I@i4aVDrB&6H zt?ndrLPVA%AcZ~NGD0ecI^E|5#KEAf4va|IFZRZe?9M<*24_i@BRYngochdy3>4UB>Yl&(6a0>VL z!L|9*FVNn9e5Llt=JNWTwE3@d#I-wNUHXgz`l5AG=<6pfBU;!GN=5xNl{(ZFfu(X?b80*p#Kt;Vb=iTF%MgbIS_*Dc;l0Ug zkna!P`A1ssLm3e9+%$eM?DcTi#2McfwTeG5Ikigb6iAXyg{m;6fveBk3u0A#jj+&< zwJy>^lYHCgLVK{S?Erq*s4Xu7B>)8>l*a*wwkJCQuFm*C9GNR-*2XL;x4pbTp5Lk# zh1Cjf$0@rb-npT5p&9$PE=}6jNY3c_H8&0P06I_}kW;iQCYP&}5%3m*Z0cg2PE=>I zUCwY=-NhQL4wy&kfqFjD;3erqmX0CSxQrlCzMK^do@dC7bj|@DhME2l>4)wLNJr#@ zX#kv@g{|4m;{!Vc{^vfQ(RKI#Xjb`_%4OvjOaCkV|3`cG|1AH|D7QrMJyT69tVA~b zVDB~55T=n6(*9NwS)rYm626c%`8U&u?EYYf#yu^Od?Tp|O>t4@at2Z=_s~`Ldx=ID z2-)1(1g6|scX3?np?Cg}1{P>1vs%Mhw!A!Q0@Rx0#mikVsU)J}5E4Jux}0SbJkE_5 z*@#*?lcJQz@Y+&)U9-+=JUH2l_kkI0Gnxfh<*{XtG_khC!uIPW`n}WlwZx-M?{q?a zW2C)P7&XxOuR_1!s>BLWq`Ghp&`SL@1I{2h2k5cJs!yU>zz7ddlu+%3P9!89Pq#FI zVmhTQE2`hzh)^Ca&?JMwxQ~QHP70uR&L2LEYW+%cy`^Cj(-V6MhlY};wtr7vcGm2ccvW3qLflf>)xAc>UhjfcV7TB%ZT79vW5p|iHADR_en zjnZ+}5ayJeV{6YXUmi5@r4aXiuw}g66DeSqh>y89M10{e$!v*!&TZ|REm3t(rD(wj z-r=B&;zAcUQY?F^tBRpOsvSDAumV!`IoRnnEwOatRZNUO!Kg5YJZhbp*fLP2f)yM% z(q0L;-7!*u4A-N}8Hz7T@~389;=At;lOfgOLXzdPt*a$o)kb~%;vz@4dj^)97pfZh z>;@S;S!Jt#8tO}+Z~bH#vw~hjoHF|R0zzE9^???;u+?g!>&?+?+}pa!sk?-`J5t>p z^-y;M$FmqiUd_{*tb_qm(OU`kk5Eu>js^C%IveKu6c~BwrqKM9_YrI zkRubi$4xEPh&JgUpfnpI1z0Vt2h zOqBd}2(_+hi8dF1J)u83qGu1%->^-DV0d0yqtIPIb$P7QUp6b*`5YUym$$KG;;-d* zI6D@%S-4m9NaBA4VB$?JF}~Akm|&$wcs}E*dc8Jo7|U1WqiPO^6fZmW1HfumAa-G2!YWH(@5m1XVYb&^2-!QVIL*+R=FjP*O}_qUmU-8 z@fGdS5m6DU&oq{sy)ZtB)-!=(vLZb7m|?{KNrc-PRUmT;h+;x%!X25&Q2Mv#XM@|2 zt3N@RPf>ZVT{q_uuDgOH@~9LGH;5xx(lBofl|}COkSpY|H5pKv$9Kk9YXS*M+Ib46 zfJ>dj5IxxXCaHjl#^Ld&5b*^cSBczR8y?3M&#is;Famg*tYgyj1G_@rm#n(;7Z}um z(h5;8e^~>Gu((uMKG7QH&dd7FB`p!kQ*lkfKn&-qkCq=S{iV`pvTy%JoBqZ}TlaHLq^)jF5VL6GKy7XXavztg z*K}J1%`)+8P$u=-M;2G=jQIeQL%tqN5Vd43ooY;O(55wK6n4)5ssU8@O6wC+lM^%J z*BN&%2|ipW$k372g@@bdmXCXz_j>C-9)36_Q9aT*dakzWJORO3Sc-3TKxtTeT;4CD zP)qa}VxDSC4D2)2#l5`!E@b!+dIzq#xd8)vp=qMQIk~ZQkL<%l%AAk*Q9KPeN7;J( z4hyDy8)Tr-P?+T!oP(m4%f>!`=T!#Q*nW<=ac&TPkOdoXP!f ze5Un~Holr-65@t=THJY*OW0u~P{Z{^s%Ni4zm5AT*(mPt5WtOdqb*|+AR;Ocsq)Yt zs+|*KF>HvS(Pco;HlLPNm`EAM7cpuP-XCO$w?TRl6E(6}sh1zFk{w?seAJJ^Uk&xu z1k@tK<`W&SwL}@a$5!#Dp&%30z2rO0{OG!7b0 zL+09x$-K{E!O3&t#H`e}(cKPRrM}NeVx`%_>gde?ZxoL4+SaG`zB*{V-PQLDt%B;+ zR**luee!5lyVBy+ucT^+B_layuHa%Z?pM=>N>yISu55kM30VUFUnF=sgz}Ncb{12f z^V`P4K2B3t!Tw*FRX$t()^fGuW*|t^`!d|1zBucL_+R)rpWA8&B_%V_4oN@HvrCSsD-9s&V0krpJYio>`*=FJ^ ztdgH)(nGWG%S}}4vq^IU- zhe#KJA-7vt7ydOqsv(l@E}v>03lb9j2~y(Np2a`FQDxWCv$&GjO!@lgnpRFJ=tT= zTx<}OpQM>rk09x|;sH)}cgtwjlK>BKsd`36BOrXOiGM4UV{|cOO*SdS;-(-?R*%); zin!6~^c4k1KH9VpU@)tL^~3%L5S+9B-|X!F!P57XF3-OCOzi);``yOA*0iL5BEg-E znBm?$CV*_f$d-i-1sE!q0>$B6A-fBhn}Q6gj_YT#aTP9Zi8^*KIa*x$RnvPEFjqBB zhcoe*-2Bm~YZqS)#QN3}BV}`!0!N=qVU9ivx~3)a*t9Z!^m0B|!|pqQalBB+R_b%d zd(bizm>jNt=aXjiE?f5#u4Z9zpvPdYFwdLCo!5Gt-I|$5P#kf$w!oI|LNAH6Bk~9l z$>6Dp?bjnmS;w_4Pure`b%e$~f41 z@p$e@qTd!2LH;iCerf9|+u?*@H>$DDGS)3FO8w*h5}s_$*ybl>Ya&;04EDeHWMCOC za$`HLYJJvS{|m-TT;$pLo^21V3h`vHE#CiCksOX+1Jb_$gj+S;Jw48`@fIG zfB9GG|N9~MfBxuu^3J}}I?B<_q~yBdMh0Znnt81DtZr0U_;bIvbFsE&}5R%c8w9gW@J|xy0PWnKU7=*D0&8Jw zEfRC1Q|~SxZyh!@kfGSK6xg;EPEeXx>i*` zbt6)6;*_bdPAzi?LK3&QB}Bs^;Bxl=L$d$B0s{D_OSSAnK?CeewZuI;J@Hn;1eK_K zy|A>u{f z`l_~tVYDn!t^dTZ`Qft*q+K-;FsDGUhN7o;*6`)li!wYD5096+i24{d8JMhd@tL@r z+OT9f`CBhT-X(HkM?{b-Tw=+YqH8$ z%D=nx-%D?0znXoWZ?kNvH7l8|-Z5TgBc3YxG17M~`7{u^DF=aH>|>CH*4ONM+&2DE zk?ZkT0jZfg1|`t3<%W*s7%ppxeRd{=17k#nra+DC35v6a}zYcf z8O0%Bz1WrDA-^Xw8XkbaQZ8+YGIwv3M!PG|&dFinnxo;EWRKC#i#fIHN9uihePB_T zddD5ZqHb>;lOn4&KC(we)(3-IL^!4{dpwE9 z)gZ*K0z{w&y?}j@#NzZ9_Lb-6H2;y7C-6-OHAmXBrvNSa+Ci(#NXc*L(S3H(xcEj1VQGKh`$ zwO-PdO96&PBf>Lp^2whtUNwyV!EN|p%Se1vX{2Jj2?mL(vQjnS7e86W!IBC2A z0vd2MY_f~L&8>5z^@?jl1ROXFO>k371onHyBtJ85fQ*s-NJRuFoL8*n4hblx|3t@U!MmW@X> zxjDDPM{VZioZuEB?GQuU-&%Jklu)ye_(VgxAp00iO(BqH3$>*+{hKfBtExRfzDi&A zOSNOd;^{uf4%&33{L*O+f(*>>+#9XB8HfbH#Yi1(F^8YmFbqJNUjWrpvba7=^G0h; zN0&k<8#j;%^H7QlAo#q`FTmaZCFTGAZRx+1R>A))or7Du>p#($XoA~IQ?UrbKrzZ}L_Y0y%Y(%u3+ibG+kgYlh zC>G(u)=3^bmC_;`xhQ+Gof>OE8(_G%8?6)ETL|zU9KR3I;Qw6%#0M0`#-?(`Mk2wS zB*$k4%tD?S2{M9txj==p$6X-N;C1vyOPs=c98P-FI+f%lNGVCrtf9^edNO*v|!0skLhBKTjQeA4~jr~d!1f&V>R`pG@@{_EE7ywZBZr0VMl zW!9)vQS9_gM<>Z5a-xQAvdEpmW{@Xhlg-&B!?=4mgtCrSmFFL#9Q18JE9WG{=TC{~ z+*CU>$d*UJi#ejs=mZln{tLcY$;0A9!!6$~5oD3~x3+xr%}Nctb*SR_-zM>Af6ade z=UU@2ArR8*SNZ@sEluO>9tjePYuiP{{rZsd6-7s3Ere--nO zkjlb#0Nmo{hce8ETCeCjR2v@{uVkYe66s4odL5`uk-sFBH2WmhuU3PlRU|C>*-4n` z3hN`F12*La#@YXSvdUM>e;NLd?=Ibt-J@$f;;XUPcGSH)3GF#?uf-h;MM@_E`iK|^nIR{t zZ)QmwAv{u^b>ku?Z$K@1W4^Fgh&hQtABwNHMddqHO<`Lb-dlHcd*c{9H)!kC?fLm- zMA#0$@JKhjx|d#WJ1*Zw0=-{6^obTYoei~uC*g-K6L4xWCl>hYd+5*@5mQJ}0Nn>_(rR+F=klEA$>`fqAUOjjOsckT*Cj;$K|(B_y4@o z^@{)ha`{RA@ISu#JFv6AEsEZm>RWr#bVoQcF>NXs<#q){bdnR~RPA~VX(xBg&<{_V zo|OC2J9f^Pwh0UmUN3*P|FnJqu~0R9$PCgo=8gigfDlifGJb1K4P)3auM>w51+UZ= z81pTD0zJZym%5Yw7*`g#$3`At>U=P<(H7P3bkgmQb!E}w1fEVIxQJ?p6mpB){l~&-?n%!6@1x*f z{>AdWrEkmHR{PftXq4Mx$30w4z?O~lr_`v9&rK!`gp|q?>Qrzw*#&yMG{vGIRz%EX zw*vHTy)9l`*xK(Kf5LHJ)WE(rKK{#WsJY`(ZLB4p7l-|z;eT!L7@$r4m*vk5uyF8e8%De z^Pqv-+hWzFR=?L0*E5FNp|{U(uCjJw+cOF<9mwJ~DgjmHv2jG&)<-EZawS6Nj;q9# zUoK@rae=uK*UmNV3ER)N62P}{FX34j(`y@+VY2Fr5ha@_X%;F?z#b-PP!#V%vBfM^ zvu0$4;f^p$2I?_I=@XVfbV68oi6*X^>Jh%XEe}7i*#;;dPv(xhe_&G9Wu755wEClw zCMjjZi(t`-Fbck$UECZ{Q2_wr;{WHe%CYh{mwsRAqU^c8`8&HmHSTMR_!ZvWgtA*y zsSUb3yE+?2nnyWFKCagv&5rnHl7gHF2h~T+mD*x{ovH4LVq|Q&#xm0R^S~LyAOx58 zMEermt!heAZlvBlYU7`Ieeu%>vFh?;GUl!i5iL-cRZH?Ed^@JuHz6=hp1Giu7sWKK zMu;4d2f&zFOWE7wOOOU<#HsS)>R1|SlU)Aq{OkeY|NagA|Hrd`#5YM_jceK>mnEl} zzy}q{WN7`3%;H0t=ylpmhc&+^icr?pta^UQo!9#sZ1c-(;LKJ0I zZO}M533b(@vABZA>a$8r@Dj?*syV{QdQoilqg6TN_z9D$hUedbM7&^z9%{PMz5=nB znn>^_BdFHro%dScrp+C>27ZuT4*|*zZIQX-DP6S3BOlCB@Y4O{j6CWvPYRt}Tf@h5 zn~Xx!W)(IBm^k?*Uk;J|zaYCUs~jo+YUTKS2c4{Jl`6;q;gO z!NVTQLNZ);fj^nWfUIk_9+LXXxaKz(a%ECt_xa;L-4+FH668GU2t~R+z4Kmpw#ooK z#ojo-{ULjJ0!}abaBFp817KZJRVwkV3Z$Z-`cYO^NE*_DAm55-)Ck9WnuVW#H zClV5aBemWkjz?a^LVXUB4%4_shMc>7hzv%+QG(oE-fCaVgJ-GUNfIb-OnCwWLyqff{V7Ne z6U^_!X1!@GN6*0jm0v6?MM`t=|M}TQR{3V;e?3=vGyCU!^NxP4wm-tDOeM`Y7@j8{ zI^iJqhJ>n0a{BNOKjjv890`6(BuO-6hLIn~W@30^nSf1^sQO-N1g4DsDz^6Ld;*fg zqOSBl!87ffwYX{{VWM_qaZitNqL>H6bq(egp)iA~yX!i2kg(GaPQuHhKZIX9<_7NA zuliR~-iz!vIZV~MbNkI^>(&KG-nH!y^Gevf${uarcvrrclVM8xY1h(OJB2zQSe^KQ z!|JYT-{hnrAsGBPy20%16jpEnkp?MYLU_wRAn1|w(ksjG$k!2b8+O=tga z$tu5-31H7M{riUOPh__g6#%Kg11xQN;>+Wg7B?kCgkj>fMjHqwg)1oiM&1rry;(&4^MQ$%N>e-q!$-AQ~kW4>?{(z*MkAQ`0{e9CLvw;{p6N)sCU z;&E~uzm!CD(U)R{y6~rjAmPIHt?XD5#3^#U&K%;D-z(C`WVgd=Zf@^quLxPLA1}Jm zkY#kkFFvGtps5y^>Tz`yEVn@wm3J{heynx=5-OXDn0YI3s{K((*+fFZY^0PORd56w zgr#0ozv%b><<3j}&^;H`<^MWa<o=yqaqUSd(WXr1= zi zsemn6Y0`#?u4s-q`6z`~iWW9kme*}^Zb9z?>nG$3F7&N!$JCoLK|7*Go}lqTrMRta z`ZdMqYRye_%9*ucrV2<0l|hwU=#R`pG0y@&U@-U=|&9mcOm^r%U%{fBYML<0H>+T+kNR?o{>ocugLWh$nG7B!s}XlrVAScN59b zIpLGK{1u(+`8~s9@7uUn+9KYaswSjDA}di%R7IM2MKu3#%AtbT`H0~w?Ryco!xKAu zO)0q;GrtvPvgDX~v;q|abCmRRqpzR@CbD9AGCmshLl7;faHFbR?v6G>ks>KaN09(l zaVk`=xPQ5z*1F<_Vs8QS1@54U_C2=0_3Y&ooCA=9fLq3~xClsydrXaYw#8ge?&J3H;x^KikE4)xr~EvkRT@T_YrHJi3+#W+ z?Be|W2mGV>-*20X|DVk&C(Hl2{MFK5E6rp-kv)6%3gDIYA)P`BQCD1Yj-u#x5p|hS zRtBN)GIZPuh3ahMt8LNxCb8>946|}+L)rD@JlfEbfu~a+3tUzk?SuB}Y27rD#`wYP zjhpy3@KE*f1a8pl+K)H|myq3zD!5@{?~)R``XosGyJ6U2LJ7FH!t!W?rtBHKm8;sG z&o?367SV2J(EvR(#87bnS@DkVtL@KlTGQ2M#$VLvwvFvf&npT`lk^Ay_q89g$Ip#_ zd^E0ok{}C$@--$UL7jct;E7ZWG>IOks}TPe7ecWjkN-KZ^n6y?U;bR_XZXYa_}cpp z&^GXdDWU{U+=wE220PfJfmWS_GKFWJ5+5SK+p5ng+~-NQw`329RKre@%Lib6TGyM zh8^3s!Cf`?!c|Zi^eAV2tIh~Y9lB7Mvg0+_tA)`Pn9prQuGft>u*85En?VD5E+9AX z*r5cy3Lwd`UNo5mTcVeCCey|L%>R0$ysvZu^p8ydYdq9`iq|Elk)R&qcKmr`N+x6k zjNoGvLC<&@H=nuCWydKR_QzPe_1=D{v}~h}>mmG6)&nsk28;UVx=pzX;Vx(EWIY1t$;$ST6s3qAlk2y;taC z4Lmu8A?O(t89qP1^G5qg$=Sr$6LKkWIV)03ib*I(6}ePjvz~`OVqgqKWbq%k`udoE zGK|GC=^qxKHH`T3OlT-=){hbfk_u@E^oH>P<|UD8t+?g=3Ap%wjsBm?dF6*nf4B5Z zwzhWz82c|YzT7s(+KHWnhOMaJ$9bXk-s_rfqnxQ%Ge+HG%;?Gn!=NY@mjEbNvWwq^ z-$RObWkqI3DPVHK2Ba{(>*j)EHdL*&`L0K(>li!OeJWCdb-B;yu3l@4&vquI|2Ccq zo?csEGAAuTrvcj3)&u7Z150u3D3{)ue>~9~9=ERzuC$-Ei=4ouIMPTJ`4scl7tfJI zLCDk{YCq$wVnU^2Ko&gK7AHL0n-Jqgx0hhRLXcdLa&`p(7adyAgq<=oSce!# zqGB1#{Qd&|#R>BXnx6eXR{n+ZC+Pqxy`%R318vdGPE`}eLPQR84Aqi1-TkBp(G<45 zaFB$_D8P_6zNq~rmbAwK@k0n2TpCdgU!JW-r$VEN4b{w_2j#u~fRM&5Z864@drv^D z#69Aa-213TJFOK@sjzLVQxo@m8S<942t(; zQ)!XHItkqSbfs^$MZG><6N<|9#e|9@vzInDf^W2Juv{_l6b>z2N8WqZyk`F&??Q3E>Mm+(Vg zR6tzF8!dU}C$vx2=NLjq28o8(En`Q>?T%8Tnk(g{{Nc99Tru?t4q8NEoy{Obu@GWl z0k1gsm3)x0y8b?>-Gg9&Op0h8Ek}_TKdJ-byYhzWjrOdmw6qOnwi^|Feg|Ql8&-^zO^!R}f1?_5d5j8Ez){a@%?r0tc$F66%_L zYNEzO+vwk`2`h>3#ZyMX-!E(S_+%8MD6_>3bz)AJDjHafa2z2+RcVX%eQyI7XMFFR zqtO{UyJgmx$#SPdLzMWxyo9^^zmrv7DSuD-@=~{SaklgB-hY5S%eE1VLk3a3q`kyarmC-xUrJO|dp$;670X4)uXIRzej>xVdRoXcjVM*$ z6Z#1X`P>1~ok4;&#Mt$=V?0l&Dn=C0ZnC4b&n2>=etZsN&+?k)Ao>oDh2;&jtq78pH?Y2D zqUK{8D*xNBz@pE^K2}rWfx^P_`ZkD0b>}l=;LH>=eZiEALk_sy7Loc?3Zr}ErCeKz z)xvi3T! zaX6(haAXQdJO;WlaNOyQPIDlZ`nN7UHCg8+R_S!oUcpRX(iV^F)Wq4}+anrUlP@~e zGzZrGVAp(cGVEwo<(La=!r1h2dYf%AyH2Of{E2Ec0|VG_B<+k;hlc~?v;_%u!nsh+ zZCuzEiC-yjY^+&Oedt>*5!3L$**s$(-9oxMF~dGrXb?? zv{$&edu-eEIS88U=T+vA-|G-Ws(mq?dwB_X;)8PMaGAnbbv8@92ccP*C=g1GweTCd z>iMH%*=ktyW?-v*Tzi;88y`Koqfsb+?(A>}a5jUc-)K8(^@qkEUHq|;lI_4liI2Q) zb@u;p@qhh%`I^#w*YPrv-(H#gcRbpKLzEMhl}Vt)=QdpYJSs4NqAVM~qxB(+@o zCzNXkh8x&)PqxM6I+X%7is;W>gj1xnpy>46O#@FxKmvB8@wK+-Twx_jKo!P=^s@lA zjNl5zXhoR)5q3+9=>IKIXTu>3U0QfYvTmeCLV*8hTfFO(fD=NWxVL=7B?DEAdI|C~ z-|FPKSiEEA$(I(ltz?OodzsOJ%&k<0(IvAiFDgF@%^TVXC~O)|cEF<}wxyRM+^NQg z+8fSwNSI_FNghwTwr^t^CY3`riSTPiT>6dDJm0)mz8Z(xuesAp zn0gbr<7Y^l9HeX2x*$WLl z7q&%BJC)K!7?DB9r^p+^*XJ34G0FjnFAhq(3&sB2yxPR$!@7Bp;LBVN=N@B2)ma}+^nJ_ncLG61DTCPAK{ z0-{SEx^7TUj0#$Ejw_HC^ICKEOJpi!Cw+3M)IMp;Nl>uylNeSkHYIPPEF>gW%*rhS zBg!HQ_WzNr@_6~rl`mlW=TBshoXPcXOmxcj5_`(2j2Xh@tX2Tu7i^Yw(+3jD#uc5C z(-&u7EP7mIZh0yDLdRY-usNN@Mtel=d+hQ{=_oq zBys+jROTlR1;1q2zVfrd)xwY%ZqCK+HxRn%YQn6*xT|b*U$r$*f%P@(?%*uf=u4kn z$FlQh=6y-I3qVD7Kx2&an#IV+dvM-Qv|nfYs0JQN+P>k`NJklpM!dzg2n5Ix65mkV z@Z}Cxgkxcw=&^JhyG;RoaPo+JA!R8vj@Xcp#YAagqS#FVQ{1fFrRsQqL9Q@((-zxvz@;@m54E4uf z^5M#@l{+ies|?tD&JoDgOyI@k5s<5 z@@FbPQ28sBzh3#D`CR**>|8DtDHs8#$^7pmQM!RKFZ zYYl&+UFGv{wg~{sKia;G&%f3FAfG?hzLC$r-Tnxlf2Vzb&mV7V4L{NT1fPGm{b@db zvaL1zz4jA){-5oq`249hypiR<-+qD5f6)FipZ`~Tj?aJCUgq;3wb%Ll$L$k*{&f2_ zK7XcD=JRJe+J~R(yr0j1+PQ+ypYLcNexWnT=RfOwl+Siy+FX!_&yVvshAG=z~f9h%_|GBG`{8m@H@n5>yjsMz( zE3f?Dx{vVr+ubQX|9f|ao0N4A^I7VCp3idkOMF(k$M`(2%StNyx^+I!?{4#XLHAWY zt8bV1oOqiPsa*IrCsMiSZPrkE-`lLAa`D@&q4NH>Ig!c--oBmBOWtM;l}q1d4VBB@ zevr@0-)0S!E8Zq{uUz@I)^OF^oJi&Bw>go@2j6BlD%ZTtZd9&)oB2MK>)u8-Dj#}# zlZV0o^-5NGt^D81r%M01)X095Z{LNl)13+F@QIo68gqmWA8ZtoJwD`F1oK)C2Uv|l z!6Uz*Q5X#h)&5|{-#C<;0`<7Pnk%N_wk&UC2SPcFg7+1nivXV0W? zKg3t^G+glX1c*i4n=T9&ZV9=$zE8q)Nfw64__faYw*3jf+vxW9%uYWnI=1W>GhIh& z;qjCu@i{_ju+kCp^-T54|t?xw;;7QjJ%QV-b;a2k{8{w;PuXN6{ zUHIJiRj~WR%{JIYfk$0}cGc>B%3JioqPZiuOLMQo!y8%0O_xa6gGgL>HX9(O>E0T2 z|Mz8|$BLBxYw3&GFY(87@YT4e^L{(sgfw*gbYEDiD|qpPfP8YxJT>!zaDPb9pIVwF zue5Br_}oS_YMv$sUmw`)h{}8_W!_gLi{Mk@E(u^1;Gzx`nM6UvHUG0;mAJkm?s9rY zCdNzG$XGYuhu}Os;yS@%A>%RucP^s*X&;}^1^{P2iHaJf#oV^2uGl;K#-}?X8VB8w z01k)<;MuTZ&*7~WoR;pC_Rtlkd@CEW$+%sYG%XAQw!!AE!Qz;Wj{(OCM7zhn=|M?~888Q^ z9*0>rK;g#h2?kdY2^|1lsCf+b-`B$^K~k)MG2ytwruG<_*EQ12_&-#RV%&hLV&YJ- zxCAb?`icku&^`Qb6suNI26FgH-T(8m7qZId@yPGM+y4mP?ES0p+0GSCv{E`;hSU5_ zGXa7WE42Wi-wTxIlyhObFssKfbT;zVQ62REVs`ZyiDT@)m#QT(Q20a91b|I7=g=t$ zC&dN{TF;OoEX~$ZUeHuuqzvzk)hnILO@AcV%7~avg9BXDtawt!$DMaDIWKk`EnV~) z0}n?-hiX*gg3e{8OA~yRxGp_v`*y~@iu!~UFS;zjD;-CWp5U^@SMnq=txuYrWM0O) zs?@{))bU4!{uRf(2Hxm^DLf~>CopYzM#0t3hiLJ{JVcj&8eaYtmZQz^XM5sP!i zsqYG$2-JOho*(y90=r|WC z0FAEgc#7B$jF*qdK0IlSe-ekDx!}(U!+=5;QGQQwN-6z$r}$*&gY3!l#1jc?iy?bo zz^ct|F3AF(^h8|9qhqrz?2A#1hYbC8=W3lsHQ~vT(-?yDF=t$y&G}oPHk^JibfR;W zz46reH;#q@k3oWLcoD-#nt2f~vI9r}6I>U>DrZX%(BC=!;A#Uf*eG+3x`kn~rf z?hEz5FJ+Zal^dmBEj^z7R7Sq?+JK`AUIwSysMqh_~GHGk}n8wf1 zkeE*>xzNVN5o96eYht@FsEo&)7niAt6d~z$N#|y>bEo%U=dAF)u%gGx9_ zQG>sS!X$`^pm1D{U@vHrdpn{F2P63K_(jE)_6ih(5)?4Ggy%LB-s)(=^FAo6=w=6) zq9tuoTU)fBv2&`og|rEt6tzC*$SrOY;O|~{$lEp!bwni2x)ZW@Q6&;^6bsBy!aG`J z1ob3M!=S>Yfz|zu;Hibon!tPG+KSaZXZ0N>a@S}v7ux1Ug4PTGAvyl9^RwHt%0l^c z>E}z+*`MN@;a`nXM>ODvs|nenkuv@{X1uHsg)eC8l1r(P`%w}GySlC+koj#VVf%3| zTTq%;c5Y|eiLCaG7ng|EOlgjDO~AY9c5)j@H=Mv+0WWu1M^xd{DZK+xaY)Wa4Imy7E@m(-}yCROlp6Kp#E2Hs51%R+$%bizK3H)r&OD{*$5B;+!Mr@_+Ydm0Qc-TE3{Xn*FnkTJzYi#z#A%MMbU> zTD z5SP{Uj%Of|cDHe}POvMYiTqlGD(|xrUEBFMyN1n3;O>noi5aD(kVK5{=G-Z~FdOabMnZ!!&*fJciO;uizP*fhM@o(zH{;uLad0a=~j zAwvg-aI|q{$B4i8qTYSt3@8oF3fd=U87DVqNuVEGoqYw@?g3FR&lAaonA!bKVRHcM zspa1XJV!gm@;#935$EFRn z7QbHae9E*}Lf&_zrhZ)H(Q>(FDXw@QrvJ|Q%_ldbF%CXRIn{C0u21ag3NMkDbE#Vi z1)T;K$1~arfOcLWF;n;UGj+Fh#OO-aIYH6I^+$@!F_sH=0VaFE#{-L16G?E6KHm9+ zTkL(~cPcKVXQG(Vz9N{Gte(W1lU7z8j|vQKoJQ7zfKY8Vzrkdm(TU#w-|C1=eI}(( zD!$^Bh|RpB-ZYuUC7pXE(-SX`->H!@9pQ$C`^$Tgnd`>ydL&;!u6q?Ka5^rb$T7?)N`>|S z44x}6x(p%}Ox=wzYrsdyHkD$V@U^f;gd)c^v)K7GYs6K)e*79o3hP;P94W$=6{YH` z-YB|C`G#;*4YT}d6^{qS<$r@|z}Q@GZ`Eb&pgH^1PRlQE5cFv_?ZB&oLk@J*jrETB z+NUN`#vezH6YSFL;#r}qcj*WqLRter~z*joP|DMu78}X!v zRhLhQ$!>VfpDkBQU(5bN2If#-jcYohJf#8bg7H!pw|ocB zBr`J@P#tBeo|WxBsj?ki@u6(j_(l@g55+bFhze8(VAIux4Dl$elSLAthKlvAor8AqQkd%_Vz_(2t7q4A?$5C}9M zm@R55n#+TyYE#e5Q}@IGI29Bh@_PzL1Ji**%5Y!j5i^ttu%D6Y@Zg*|-{jupeur$P zbcz7o#5gl(aQR1wxaV7{`q6SGbR zeL9eNN;FT}C|d!-Ja9>DmWVM|KajKFazGErw8q>;(s;B&{(oQgJz3?(^0$>IOW(yG z-*aD$&CaxKbBa-jZ}St;O;PX-#zv*~ZL#d*vhbN~OtSmwKiy)N{i!#cV>jjdxp(x8V zCvqetB~Uj0iMkLO?(Wzs;FzKEVNyuypzwm$n;K>$yZZm@S^2ecx%5)@>)Gq?;`Lwe zh^Q>Jgj7cSI7ba9$vqi0E3(`mN>_BAl2}h9kl4hP zSfVh2sxus3UR)Q!aJbJV9JFO}~8|6*3z!T+Da|Nq5z@&3O%k9wtZ#LP*8fXDCM7}RQR ze`&K}zFM#KceW$Oa{}6g*gc|g1`vkcP~iQo)E=C);k2G834a6c1TT^vR;KhwNJKYk zR$yvY>0rdg=0yZ=a!&IU%vIUIL-hF3UP4*f3eb$0ZTc_@LL?6XKiCV=N=K~cQz=7Q zBd@6M$>o2N8x?1)K~ zSo)gr-e@F7eL+YNV*}L`@Id%QnsjjxWbNrKLj~AIu;NO|FDq;(wUj=&M&rWH3!LV3 zHKFk-nwHqbMdo7+)Ia9j-D%YR?8*s0D!d=uVb>9@KT*;?ujvZ&*CL9-p^x zjPl-TP#lELM((o$)|{0Q>@UNeC%rOXXP~8Pjk63Os%d4Ruz#eZK?Q`+zSMc1{o6an zB##{P0}sPV`jD)!2>PP96QMhFsUyzund(kLr$Kz%24>A0A4ii_$)JF?1s)c-5+|KBmV-rR28Fow~b2mk^9I8k7@bJfllb>}7$NR~$C86u@H;s4Yt zM@%5jF+ADA-jdoAC6AY?DbWybAe0k}U=N@2AE8 z{SV5Il>ScXqZtejXV}cvf!sl&A*PB^|`_rjB^PsfAp<7eo4J zkF-mrV7--1ZWA%3e-UnX58mq$$g9eii{Hh!)aMsTgB-0K-V5L8i0OMeW#C^Fz7w0^ zReY!>cm-Q|Nk{D7Gbv=jaS=G`0v9uBqOq}%c7Y*kvn!_0_?fG1P;@l8UVz%5xF)fl zhfK>&ov)sQ(@I{Hw9V4;D)ToNG%p>mr@p|*bLBb<7f(mkEA}@k9r1t9B=#Ug@I;Rq zE3vZ5z*xu?>fwMd7M7_I+1Nn*23X>EFC$2{{QvX0@|CZ{zw>D6k7vJq2KWCnon`Iw zL?fXwHh%b#hMQgGaz!+Lrn&X8V$(MuS9JG*s>R5(g;@*Jmxw5mUrB)lIBo#D# zspE*&!X8qR^UNl^kO^{r4{c$ggQRh7XTeE90?k2O3Z7pRUI-=!{5fepE4V-a!2B;) z;Zkk5_ZP)R=FP&kv1cS+gXW&>h&vq3ozR&PU)`>dZ0r#m7*E1*D6rAwcaRtycfzBC zPj}|g1XC$P<6;?r+&C%imi7th->AJr^N$Eniq8iB7kF4){15);k@EMK-p+pOoyY&| z>xdY8Dg}=lNfw<^X(#{YWImCMaQAQNtRfj{mHEg@o)E58*l@6!KJ6k(>JVwJQiCCJ zreF;uF2Z0`@r{Ds$_SFRau~ zi8RG52HoXcK5LT!$;98P$Yi-Bgh&WknH2$*0^opG;kVFs_*2Vs_SY7gIs8u#8Z;I= zH1G>OtW5sU+%$yHxp#3#+}ww&$Hyzdk+I679)qBgxdOy~#oRh~O^eUU=|U00?OI$A z{!h3+aHzcGq$nYO7df5jHI?q9+zzmzE-F|ggVHY}4}|)2wUrvfW*Km7__C7%T5ZlQ zXkg^*s#^ZFeBv4T1L#xmDf#tFw0b}qXaBp(|Nlz)CHQ||$hv9X|GV^Cjk`PI+UA-j z^oK=btymkOw50W?Bg?+M&;WefT#3zP$h5#w>gQ6ZO8+`|pKY&l1i5WhMqDiNK90S* zBlc_}!~~-l5sRUqR@MoL3`JfC>(3<)o)7(zkFM0`ju)1OMeKuCwmTcrzkBGhAIdfs zQlt4T!OGEy;jP^39Z_A+RQKd74nw*Xc~tVx2SnZ{ae*9m-5p5+Ic!9bg4P(q05m2N z&7X(bZ)m0VqVj6T&8|q$ACXn{^ZyQG;bIWg0MPMxRDdmVyTa1}nFQRIv;Xk_EyDl% zq0+pQN8@1UH0Oasn~+S52CPrz$q$t6;ljExE*RdMsE%1(Nr-d?fzblvM#kD5 zpA#npXw|y7<_R^0qRK&5;a*}!&(`wh>i+!p0k;>DJlyjaJEu%L?OZ(GAVu!^jJiC{ z-Ccz=gf=4$ZgHVp&Ce8{b;LzZ-zyb5U=TJ(&>Hpf35E(A(k`5n4O*utUk17#nsH9L z8*}!(mSZ~_nU!vc=)msmBB&+&s=}_bFbaSn>2PP~ghXeeanJZ&k7i(x${%B3$n0@h z0T})$> zPl(>4!u~W@T5*M}k!+CKEXb%@pl>z_{5g7vdGBrpnF*E6|ID8;O! ze?MKiKbt$}cD?az=M8sSC&!;w^xgYP&LS(%cf3#|{#S^oi6Pj=%_W-(x!~x%v?tC* zcUg7uiLK$)9RMul`i_-6$FtR6%){cdlI@k$aBf-$wN z+@%^w2hee_g7wga^!MYN>rh=hOsNCBF_K6%Kv7;{g+R*5S_bsszRqjd+v)1u9ya|+ z>)GoMQ(Yvu*(4$BQ3m{NkKi;fVQMeyb!_ik(0P@uIy|xS9gmdgBb-QGil2x$@Su^C_I)6r6Y}!?H z{l;cb);DGewu`}na8}?!Lfzm49nD^tPDz$VFT&Ha2)n|2@!19b1%849E8Ys1^A_bD zi>ht7JY-%>FkpB^_xL3_|DXH+dn*@|=Sshp{kLN0+uOtq3)QyKP}f_q|Pc5~e_E1_pMN5;viOBpc9PpJxN0KxPu1-w>YyTea@Evyl3`3VxN zK3*$#GmB0VkcjatVPZc)PF2bh^FQy`taskDMclua<8eSQ*Im2m(IWZ#ulYatCWZR{ z*Rsk?`SYb8%l>}$wR3C#*Sli*-V4Mxl4C2JrMay=dE4k#P2P4swU>Q7G|-iCY-M)t z_yd?;cH-Tq>P%1ngOiqSp`f-{V5Xh|iK2yF4j&~$C0X8FvU_ZYGV`J;(5KeW)F^-FsK;94)p?9OD*BHmmzbGod zjoKmrCes9#Ca6sgWLop@j&I%P)@)369Y1*^0n8Ms>;V&_ARw_+l?mB0qBn5k?n@@Uy652d-DZPrC{ z#UJ5zzOO4@@2Tqc_#KYi&S*}AnP2!8EIhF5^9pW{3hPm}poeif(KT}KlpzUmi;ceE zu_39UyjU-nlDj5K<@H0*##g$RI2Dl~Yf%;P41k4WL?UxV-=&|>A%(YMnYuv0u)MZF zh#=xHoveC3&O1v-o? zLV#V{%AdNb4}=^(k)`KH})gfrDQd1Gy{#w?-T zg0{V(d!@UAdr>k^*G@5jILvo4%1Swo{n`aypu;SArYX~Yb&*6+td~B{-$WvR7@Y%Z zakG1c3Gt!vClwLm7vgNW`6E-KL+|Ri{NJ~N|E*NY-vR&s@5#RPY%l*g_;-!dT~T-g zDNb+~;~K=gdW2?!E4;Y2EXFr?iFB_O&6*W=k)G?9)?b)kkVX!>haSAV1d#qgjRhi@ z#(7M3GCm|U4F6`xN1I5K*wsU<1r%30NfsA&fWjl zvPz?pm2WP6M`>U7+Bvw#N&kt)zOD$uXHw)ne%A|)G&V3vpcss!3z96Ru$<_fk8doy zI>2431Mt6)1Z}*p>o~&`T=AIhRTomoAZS>V2LwCqVg}r0(SVKs5tI*FSPyA>veCh( zxUhSREZ30Ms0W<-ifPCKeweC%}fCN}nR1F+q zcpA?#l2Gjx#ITY^94IudhJ+Q~A4RVejZ3n*`~ML0MVZfge(Bq?-^dnk*D)XxmP@y*syRp{YFM*!eNpU@5!QAs}U^ck;2S_C->n&w(BB~Z(mPc(K zf=DFU6@qu8QidgcEsH+js|qzNg^8Er=78z)53L9n-x!}Wl)!cW_ho;GiPGgKnDl)a ze|*n;ZFa@ny_fkR&(`W{1R6HBGq)qSzO>PWB5dQb?#FrT;fVxiU}V){SI&b?#j510g^JpB-ETrN4_8xCNF$eZRFKHw{ncxQl%^`3O08l*l?ElN zTDUku_V&7y*1LDwEl#L>MRe9K5+SSm?k_x2`08Gbap%#l7`dk>Ql@`KjnBJLSr|eV z>L?FY(GgN-VU`-@!;^;yljQLP{NWN)!{p1;(6$J+s5bw=qyvPyaMnr!VEmn5>C)8* zR_NCV$yeIyiyDvMpPuVn{%>D)Q&u@q{-yGE=_~xP__gz?u5n6N-`Z0|{+!J=(ZXlp ziT1VF!BPriKDR}mNBUY!*EY!0UtXu`*bT8F6su~a{rAGmGJGOLzXoIh1nACXMY4d4 z=%L}j@b%%Z{DKr-7`xEY`80LZ?!;nQOuOF zMgT0ugQN)6UaqQyLSPjZT>=CTWVrwDX3GLxpk1jqXJ!$W^yLK=NO8zl7GR=69G9S{ zFzAgHJy{e~)3w@saaM}Zlf`X;z$e7_t6gz#&r}moSMgHAQ5J<_63hz}FV|z>dEnf7 zEs7U+#jHJ3O>i>e4}5yoWRosbn$ZdHiBjgNAd`%B*Oayl98e``&Dbv&wK*ZT@ERU& zbbj}4)0YYS0g;@;*wJ4O0!&!I*%h`1YX^oshyOV*`+ZsYx2XUBG=KO8f9*_lA8=Yd zfsG=n)t{T42hZPYTD>+P6o%2)0O4qKJAGjC z1&s;`uvi|YjH7Cl40+~Lm0E?kvjafnfL+AsG6srZlAuQaRJ{PG9d(6S{of+so+D8_ zSYjL!-odf1bTzbLY9ayu8xgT5rSZTeK!j|h5+l&R`Kyj|4~QeclCJ80(llUWFW^r` zSmY5J0|^bE`|<}!NhCifF26G1It1Rnbz`U8)no^l*%C5v5vOISW_ILjR!W>bMn&5} zLIfFeCQZ5-r}UV$?jOU_ouI}eVI^Ka0*e+NC3sB^O zS(&0=F8660&e1mxJCawTR-ez!t8o@Xw@zkObi7h zwL}dJ{fXt9&#y`G3e!^p?zhx^$Tle9enmq2u}01_qZJmRA*sF5?TyR3nw>zSMoQz& z@ImxP6WK{fF5urP`Fq+q2LsC@cuE$Fj*`c*7U`2@m9Vp(_;PLo*5o^n5Uz^8huXI~9C5=# z2K&0MMj6m*n2^Day5R%21Fu-*^(H;!LY zM7ceVi|o-;Czj{7>ZI{Q-@2xP`vQ6Bvl)a85n}|RU8BPjr<-n<)HKxm)`C+bfujEu zL9dP5(GkF%&*UBF;c*HsLf{X9lZD^4`F)Jmq;U_3N0>wqr)0^Wn{_nz7@Z&KYGwkN z?AegQZC6WA zucWiy{j6!3gj`fabYlRdyn^Af;7*3@+%;WIC?GqRU}Z*T=RBcPpe~c-^0KB;c4g;Q zMD>6P-WJtVVLwPh(0CT0F9*9%YH>+4O(Pd~7UP4W(}4mA)1n#@_#3A+ry{?@R3K^K z4{^k0T@50bsiwJrk*jv5Os1}jrm!j^EUGmj$ofq8acO|0t&IzLD$|3~PrG`1?=bu! zwvYyQ5)ksvJaP>kN&||GG0yKR@qgb*bNBxbX#U?nDo>YwpmcHe2hP#`-^+hyvnxjF zBphMfBBoqpqhbhTh>iVS5liDN-ZoxmMLZO*q+Q8@>kHd(ZFvzR1u{$&+)U0|7F3vT z%~4c^R$MO|4=635$qvGd;WXIXmi)Pa9mz@F#a+=x@0|!|G_m=v*24UPpg{n$5S*9` zyEe=r1+x&@x&o481!`BfiR{J64ca|bwy;baDPVb2(Awu$Lrn{>DinF|>VD2$%sn+A zfvA&evQA(6YdI?XCM)v?^(6(pq48}pw*iLE4{`rgkCp=@dPi>UI#gV`orOgcsDQ=y zZtBZj4TzZ9!{qu$Rq&LXG{Aj1@XUKi#TNYkZ_g^vmDfvuq4a_5+xK#9&&|I*)&0C( z{)B?Vh|8ZCDX3FDH^uvw?(@f#esn1hHHYroJM4ez77=x5|ACa@BwYYx$`FvSVF7a<17Z7BD{9Yt6yGFo9o zuw{dJ&5g;L`o8K+QZO*GA6`5;i}IN-ydk-LGHPMg_s6<6@uAxIrtxnW$t#}u!3g_f z6!p9zjIoq$&;ojSI--G^&<1b0@XlF)&j!#4`{jky!NoyIj`&9jMRgiM^B@&vwpyUd}LV1ZDk- z*Ndjq6Uq_sYkyR!P^-J)9=X;cC_^`xKdJ_;O{IYhY7gUWaV46_;$yXi?ib9;p1q6r zn00faFf_1h@ImBJLU3?X_XX+bq;VC|Qr6KtOAJa&HJRbF0r>HnumTSJXYNODBz=B2 zKy85k^Kq5C|Gz7%tdzUuJ4$~z`;Xc0D&E|8<-67HF&)Z8g0~il*@v|n=FbMqH#x3Q z_H2BtJFByqNEvq-J)0=d%N$qB{;6zE-b?4EB)anuoo~oZ#_vD1LeO;@^_kS&fb_8|Gv-h3UNt40>biG~?8&M|yP(kw;OMx@y16neS)Rl7P5k5`fr z7k{W>qiB_Xcd7`=Muyz&6Wy;k)tsO?;;Q)>ZpTL0eoNfCTeMh*9<$$W4VFMz6@JyI z^9~qT&lL13o`vh-K|hZgAW?V~Y7sdSuuHmMHYrcg%n`?68iG&Ngx%1ldB7R`$g zM`8K-rc@+svo;5yrm!Y@l&Kwgy{!9xL3T}6d6oL#4EFyES&MJM_LjQKEOurhft@eD z?Se*o&IPKnq<~)}B07zE4$O8h0!6>`2tX%G+}|?^rc6O)u*;w5il3VdYl52_UxiDm zS&|8~203BJD2-V~^p9!A0N$&)eIc8#1hc$eH0tIEZoR_Fi9Q8wbyoOfchRm&!qBnE zQPWDRGyy1Q&e$cQ$RL0NFrdM}ld*516nLV$U{6jUO^7`C31Jkt9%R}%KC(!V$*j452jR-2QodfWplpJi$o| zH|w^psLClX+_XngIXbM?_vmu2_fM9Eu^dzgjcEu|`lfp_L*AgQyqEE^z)u5i0!dh_k00;WG;ic|NEM;mU0Ua6H zKAQ=NX`59zfXo(8OJ%E`wQ%?U`?AXKu6&^Uo#o3*-R%3`6_5X{9@40H9gXVFed8|z z=QKR+^=OX!y$9zLqNTRwE5}OL*4O6qP+0z+hBBe@@LqcVimc0VDFlQ~1o%E&Ulj$2 zVP#-nXND0si79w0OCPII=E9_7SNq5Us!Ftvc!qHYiYgxM zgQx3F)pmDX`lq@lKlgDtZoDYlOxG2&>TS1q%2WhWX5S%{II%8}*3D$_&iDw@!(cQH5}Tb5w9GM38*gn&qE&4X_F zcb2~{teeVPpAGxX?rB8laP^t-GCOkBfnUb(F|wll_A)m@Jil{RZ42##jgNF43wp{J zrO0)C(ph|1)Oa#p2WDH0zRGq1>mms0>(^e6i%HlFUa`p6j~}7azv!C~Op+T7F86x( zq%>Q~!1>5Bqc!E6R6#sKn^N%IuIn0C`QET+k@gvbhNvQ=O|x5IvcA5&C7yegWsa#3 zP!xK{|K}rYXa7H#Rjw(2SNUV5PS(jjsO#T2@9mNqfrNy3BqNstfRVCYaS+lqEecyC zbv&0d+t5mCCfIUA3axH-Btaa?=0_m-~ao6|L<$9in;K+8BtQ= zHaf2Ec`7RdR{JjPdr@ zwkg>XILo?_mk11#{`Mh0Pa3~^grKBf1^c`H2~Voan;HusaS>5^;gW{CHtLxLyBS2$@&zvk3r&7Cz(C+0#x z9v#nA;W4>rXTg|WuDnX;dawzTW-LH_1%G{E)#19sF)WlR%jb*BzYnh#_}?wHZ&&}{ z>Tjw}R(^}&e><%STkZ@1D`6@}GfNIV1)0M@zdI*sa|d6oV3IUiQnHWUrEa2yy+pAfLzx|p%dOl0J8(+=S?LKAr^L$WHM!FLB?_io@y z>wvv_h6j=`c8@Kirl|W8^;)%lnUo@Z`SsK#Alyy|TNm-b>CCuycszB|m$3>J(G$(J znO|Gma3xHZi2{l?V5~G0MLGR~rH{2F#tz7lLDD|FI)CmYUKS_m;mxJ>Ls-*wsKg70 z&?|lLgy=WA5oKe>^kd^dVTCP>3pHD%Az~BA?gzx~XsgB$$>?R&h9|A@&i{eRR;BjuVE_Ha%D?8f-|=5>v@X}4o5*8L58rdsVXl1nv(EJTtfgqE zjCG`kc-LdtDNYGowdY!wX{$Aw8Bq1etyU^3(ikk8;0R%!{NDF{hWzC+Nr`0!4t-Mu z4ti_Il)Fpdq?0k4wTu)Mz+BMh*v^aPk*+j1z1JEDv0ckh&U2|qdFbu4Joe`$HOo>ivu*MsK z?l(l;0qS_8)zE=%;aU*R>rnv9&^F!_5dLlCS=x${)Q0zqiV^q6kA@;@oa-&lzGxc)Ey`0BQS&CIeK>7@bQ)Snq(6P$Ej? zfNz!HkOfJk(o!He2_h23{|77AR_ZU*exdd}`oAYCe~d5r)%-~7THY5j{8i(Dr9=oY zjFjWxuC)HQESfpX+<422@yG#k7&!h7s`WBp!?ID%@z;6~WGihd@*d_^O_|2A8 zji@t;zBG{3jKqq`)^9on>@ce)A~~Zf<^Mldseho>u6+>v@9$SWH{$+p-rkZvFB$cW zY<^;DpFr!1j4lnj$_Gxzi_L7zY$vV`90zvd-54#1zD36qqqnyZD&7w|0f6KoZo8-T zF=s0FmDf2TBcO~HyT8zw!+L;C>sa&(&x>} zfF`G4N~KpGY%Q8%MK8~0LAEIr#Wqj3KFoeOHnE#g1v2vOM<9^HWkLc7;YC6OOxXQo zxBS-ZhQrI9-Qg7Mi0nJ)=$QOaq(h4SfBdTO{~(NeQ|aP=?*FH%zqfjQ<&U_#-|qhp zwrpJ-ls!B!Lw8)uedPGJa~5<9={XnR~%n3x!{(nRFj2C@Pt06PsLylWE4V#;(v z+_uy*ecT*WE@6cBKyu}0aR>`%^+4+eyG%}xpN7Ouwny6zbWSO7-dJ~w4Rj-^y5!k-IXHO)6ue7beDb0WFUDoLjuuIiNAZ{eFpmkwNtVf5>*Z z>PdqdZ7y;2v-7J4$rRQgTq;or9c0e>)me;5F31*sYkZhURw3aZN(-8wYkk~SsF~4j zVdM&(5Vo;Lq_H$)%l+YvN{(!U^wFY-4$31eAP2C#^osLW7fq33aL9dp+r|Iz|5t0j zT-&Ms)$04dEByavqxD%1+04Y!_(PWV6 zh)A|U0Vv^r_f_i0YCm87XVq6LzqfMV2>-wTS8FYa^B&8&f=61qkry$p3aRh_z&zQy zl}$~+zp|f2bmt7)OTyW>d#dE+sdK-5#Rf}=E2t8n%+hRHFV_W$rg5t1}=gq3mqhP-fT%)m#!4g zkDup+Egc#di_$OT2jd_m*N|a#8$iXU*nHtZivYGGd(gW^P;eHFouQTTSG$K3i%jjj zy6ze?K}F{)OPl?~4aXv0J0%d%=?3|>Sz}TPk>newY>~|y(nC*wEtlGde40sxfJ3lsc|M_amb~kMHx;NF zu(L0{B9&es1ys{=Bq790nbciSgf?UNEJ-5 z<3tl0csl(*ZuQWZJO6)y_`mjM?O=7g@)!1=`tO@RX?~*hfU~_BxJ}y4+nW-=6Ht|) zB$dS=^tQnGY~K3?7PK9pJ?f{MM5u5Kvc&|t(yl5eh5g=`mvpL4`s!Jzx#FjzAF*yuBK z2?hBut>$G=T>%UZp>lsuN3(&q!6pZ!lK5Ye!sNC0lS&=zf5^~HV;r1>jEf7se1VGp zVol{F>yvuv&E*B60aayQx>i|?*MZ&q!yD+@>HD|ARv zacQc_diS#s+byRToFQwJ*y)d=s&DuP`Yr-|)>c$lq}@HR#89336yo)kq=Ba!8M*c( ze(s`9P0i}s>McGAQhHTV%4&Y?5tJKRpJ#R8@_k_Z>Kq=$<(u-#fF3Und9MCaSrD!} zR|_b^Lu(CMO5=UV4)GVI%a5H8&{Le(H(L@1hW?X5MN7_2Ii(+xvRE1eC!N44cG)t|FB^{XpFJg@QeIY1l2nK7grvv)Nh~z z(7o0Fsq(XYk^PG*fyeA3%V4J`F0$uLG+9@s@)*Q?n^extQdBF~YL)y4Sp!!|U0O)V zDn!K0*IQFs$cf!!rsdRJhA6=fD#h9JkJd1Up z2{ol)z?4d7 z#yvGFAuQQ5`AgV4U=b_A)F0^^Hu)(>&CE{G4K0C;UzjZ%Io{RQ6EX%lxKsiQ$}U-s z2jLK@0LR?cdR%s5qM0%4Bkjb{%`~JYJ?dt|Gy_iJ@pHqNi^$X~6r=u6RHpMP&*})~WK5E$V#JNQvtjg2*S;5Z+YWp}c4O493PikwcXEF|Ll{l@esnlzQQ ziS16%k0))hkSKa_&Zaa_xOikBJ>0R=dWvmzEJtZ~&L!(^1=fB1i| z)c#iOz15YG?7vE7_x_eN4W}A!WmKCdEbU=O%-1pS;F;ZkAZUR@r$cZarTw5aNT0%i zIO_^$T=zg6ZT1jgJx9w^kJjcW00seKlR&$1IMvF+#+;Q;Xv%!;3zzumH3?4JzlT@q zjH#kYyAS!+73bE^FSx#on3ZB7;>i|ZkINd8ZQ<|8)6NAhUKZk;O-Iz2G z`bDl%=xZ=H7q{s^dW6e}Gw~RKqv5_4q5I=4uYf!^-tuvV6H5D!!A2pCB8UQ#JHd>r zOK~R`CNWFQ;Oy`F#VqFZX!z&FZHdj%y(;qxdfLrvTM|ho6_;Tt)35Tl_t>O0;1N|e z8|Kg@4$5o}Hd|TLF&>>$-V4UE);f+Qm}<<9pXUS~^t4lP)J$sD3>}0p+efXG$5~xi zDW89k8g$4?`Tw^n^_Ocu#rgk@m48=xD?9vuXTI^}mQ)dEa76;&-!uQpYc0tvPUVyvC$`Nq zQuhOZkzUDZUvMm{+uV{cTBl?cA1LaSqGaEtEvId~Io`%3o*xSAec`JBZr1`+>ZQ6# zxlT7Ww%1+qLeej#1t+3{Jsr;Xm91HsyvEM>;iS#2Mr5&ybJO~l`zR5bc#4R&P#5uI zdbK5a|pQwfTMuN+Li@U?WM8G-BBXOb+ooT~9V1ti9=3D!Fu8?C2tA9(QlqXb7`Z$*jTDSQ&Wvh_dYR ze_yK9exB-|x2gX9(mQi*n|HLla_G z;V)c7FHb|Uu+$NxId3J}v#aOK#V(ChK15&E+g69a! zBo};S%MM%O9mH|SxG6pCQJ-K`q%m!`0tuic@{u8z$PCKHc*JOXOW}j~D7uhw4y-i> z*AA|VrIB<6>CadA=LI*bAk{7!9tm_hFav0O6#VS>wWMf!tg$`b{~Kx0+4URPQVX}a zSV3c7z#bU*4N*d%#*iAyXcu72`suFbHLW#gA~GPsM67Wl5*9Yr*fznTT8e?S>l)_I z`ihV`q)vlN!`z0$5)F-UNsP|{FPp7Zo39M%w~_NTQ~)l1sufoUf63kd8A)#VVxrL?4V<>!4|K9wJ9jlW{0+HyhJ+}h&ewSfZP&6Cd&>tg6%Zm*X{ zPO%QvZy_ObRco6=HB@cGyT^Ft*csRYR2I#U>h=H%e6+x6g-Vjm( zFJy|(w4CO91|pHXJId^r_)*ws9#z8sj#uh8)_$T^tJRUkMR=*qOZV!U`tL623_>sR_*OJcIqjhXScQS$odCK{r( zbp)0s3|yFMealWg7~V&)vCR*)OiFg5nc z?d=jk@!(AHEJ$KE-`9FoM?*3wSdq|aLO zhHXgF!}^ry)zoF>p~CL|QkA3QYqG*dx&F}Bi#5G6da$T#XI z98%K{F^F^ZY($hg)VIFz%$9DS)d-~!1T&rpEkNB-I4iT&QhW6!V zANF0ACp2H#C(lR#0VmP`$DbEBSIrL{LyoN+%Ik$k5}F8mB!XLlBO4D>9SQpG_7~fd zXP=qK*>h>*jrN|EQ#rWPE99E$pv6Z}c)Xg3zfV@3XUZ+3^Qob~v^2M+uszh zrOwFoOL7EP$YUWhy-<2H3u}sC>x#An+K)9d?#Pk$IgTdA$WRO!5px~3qVE5LmD|++ z?|rp)_0Lv+pzBmmvh)*I3%Uj#CG;w^5!LN$;{3)GJwqVR6M&tJmjvxK!@Yj zWDBa@G{(ili6N4UW&;@qB{PFY#&hlW>k`X4HA#%)ymW8-cP?1XHrx#5Ar{pBz zw~Z(`iJuDO2j#-Qg*# zzrU{buWNJF|FrVU?|A>;W~D7D*{Q5bqTzWzSAMPx7r2e|cXZLL0dd3hGKz_lor_;n z@E@V9_l%o|+8?niC8stkeWmQpILuMU!>DjjwX6NKL+lXWNB04BKIa@YOMh?9fhuVX ziBeR~gbhbYLwu-DFLp4_vl16haSZ3qJICy)CKu zsfm;0UBSe;H$QN38Ab_FLuloU??T#t0a*~M2po7m?9>2lTKr!Sw}A>KEJI-H?nqYDB@->BI>#j zQiK%#m|T2mi81kgh^1q5_NBI9jp9#+m0U3F_uV=f7S(TB-dBvj0c_JwD;@&Z{+FY)dqq&@}^rOUDF5 zA#-#zEJ0uRDI=;i_lwj`QvV?Hg>y-^w^bX*+Yo^PHB& za|()poFB*3mZ=SdMXZk0-5lEHDc!QeE1pH=E1G1ba7AP(F$(-na;AYzMIHD$vI%o% z9f8o+R5hvn(k-5Vx{RBEQxKre_*+0yQ%bbXE)${7pDXxUj@jwK33EKxC{~dP($GrN zXT=YpO^e3_xHbZ?7vForS@lJekZ0c*w;~-*<-7PF1&niSLPQRIaOWs zyIfw@{5q`?!QC|cl;C z7=F370rjYsSEsl@dJ3g=HjYtXm%Y)J{`J&EhFEGMsx8HrDk3g=xEO2=uNPmlGyrbi zK0NW(Rqap8!)pBC>*JR{X(sX&bR^l>S58tU!6g$hm3`4t7mtNDoT{ejllw!HO&M1h z_$s(Je1k*;Y)WKy&w(-HaSOrs!d3K?wo)9OdUE{zl5CIZr^uCr4<4R0E29mDvnPWv zx2zu}8zL=2ez&4fBl~;%*~xe-s_`3zmHqqw;>tHv|NA%4|9rjj&-mh<`_;U>eJlHJ zX5z*1i=H;l=U?x1oJ5ML{oFT6+($e1f~(eOo9gyNMiFV^Ae`|0PfWq4x4%xe?3Ain zjCaeHQ9i@fZ5%2OMkTEwGQPNd$Yv*_)IL2sPq5Epxg2-%h)rOA0e@Q%)T3>nY-uzt zN87h>I_ZeIUu60MWWlkU0w#3&`0bGHw%ljN^V0R{cne&DE-;7<4%UXswnFm%+O~wa z(MfsF_`R1fvn;iG8F2E&2$gsn*n&@Pg5e<3r~80WQ3vn}i;n?Vn1z?P%J75>l}0Df zt=|poqP7}dOy%@-PhLq@@t;-y8!tRKh$uku`M_nx3LKsJk-OZg43aTR&r9!POhTwC~pRB1g6TYYRIOAxZ2gY z%HyGyEgrCOJPYZPgKbG^PfcXxV^Vvf4>!oz800`{iId|K-e7vh#!HY}T^@==nu2A& zv3;8}J{i=h#Q7f5HOj|*4^7f3St#hj$|;o(+75HfDQ1*Iqyd7A_gf{#12rf#0ORui z4^}=8|G$0-3gDMkKUVnze3|!az5RLSd!L*%mqm9XFhy&&c)U;mQTi;BK|f$ioKpxp zJeM%!f-0>ebVCT@0VzJyc8cP=8S9w7qK{$%LhA_UGVlkucp__HAf`cXq*Sz{Y=+!T zr~lfg&AR$}b2XhbHT9+B(eW|)?vS#?${2%!05)aTB$@6k#$ zGVE?LQY!QR%a0<59rVat{Qvva|L2d_9;^OA*uUSqzjmwbN3g1(YZ>X1^cp>pXrgK^ zr7mkPs=Y-;7oi?B7xt1naYAZzzh|7gnBiK^5{jt5y*h>(AFUXOv4WVePp@nrw%w7@ zJRrF{My3VhdM2(sL#IqdIvx|d6+puy>)Y*zHKd6n<2~)bZEepkFKw=n^CWu0FrPWMzBb42soa5xfwG?%saTr%fg!=u z4vGBcXWI{Ik2dz#|0kiJmbe1k?fJmuacRNGd6J5wZCm#@@o**g#^Ocz|AES}O8v1~ ztNI4`-?4oj|K^qL$89fUY|mtP;PU+;ftuUoEZ&}UI1ta#Cw@NAevCCimokIInB1U` zELg3vbrme~`>N|&S_FrMZGm05+5T3dzj7A+AI7s!2pHWtyOV9{mQPK*_2G<@Gu)qf z%w^*MJgg|)iN88|34=v-`PmT;ZO&Ze%#J^;(vX`c$!V>FW(r3gL9%seNIC7wIP@hg z7T4y`*(q$qg-F})u`R@T{1+B)vi+#$sL{-rql7Dc@(cyUP7PA_oi(#kPKkEG@Df}n zWi~=LI_y$=Xfvb&Nq*)flIYCLJQ#dw9f40^ECvGc-R&dVV2x&m1x)Vxqf$o!M>Uub zf|POxjMkSAGYEZU(un*9Q>265{{{>cA(S}sGE(R|=#E08ifnDk>rwu(uYaNY{~}y| z`hWe)>L>Wk_xP_D+Y$z!$&m?3I{*U>?E#;b7fOyXqCmIer+T>lt(mqI!lxSF%0Mv_ zJMhRbWmYbgS#cggkhPA=5cyT!OC*Z4o->-;_7gZHR!@gzbxYOzy38 zSi!r5dimSn()P67P&p8AN`m)M^=#8>l~Q&Q|0WuuB{1X53ECgNlZRPeZA-fRSWZ#f zNRR~J<`N`1vzqs;SUH$L-s=o#P&u{&%n}D^pRKI6( z7@z4J`XdWm!OlR2(-iLs@NcCB{1IyLXJn7zS z!NTfGl0h%62Pu7moIusNq-_e_Il=hI^*EpQGVzo!6i}eBJ5IKrwCjEMK!%%^G8ySv zp{aQA7bj0|i_|J3D7Pe}oI%FzKHuD0TgN@Y$eNY|ZkJ;aIK@Nz5fuL&J3;+zrLiyl z@A9ijozlOW&i}#6jg|UWYJa)*VD%4FKU?|3d|Ce0ysRy;(qlQ@xks)>Le9^;M{p`h z6?&OG#2yYEM3Jg^D^EbR`Du5PwZ+6l>Uwh~bv%nI9~gnG;+X55BANjiHndMflW z40Zotd@=W6Bp#d7gHm0;OM}2rkwy=m<|XatY!Df)NMaD8(c0-;ncbY*mV4+xj*w+r z*qobP#~GHKKI(Lv>jR?!Jqioad~f>%qnXYT4@kyUBGGD}v+}s<&b9S|KuBTwU zPR2J5QV-3G+7i}2*2v&GjNGkYip&BzUgpa<##>u*)>PYs!aA%ZXUC)c0x}U*6@I+^ ztP5N+x=|+F#tSG(HVz`Q=otj2PhVO=GEBGt(fPl)@;58>AF2J@+H2Kcg8}e2%ZLB_ z_N(Sc+h2F{e@%wfP6XG(W$X=xF^^n97L&c=Y5--AeN@Ku`*xwF$%mKJ*CczrE#>p? ze11u zt^CUO?P<>$IMd z%f36&o|T!((nCu)++Q#f8laPTW@(<{BcXru8k0!}0BN&T6C$-I*ehQ1*18!( z2sUP)uD->E1w`g&@koUL7JuxnZBCv=o6cnwHO^n(v{RMaN_nFdGzb!Pv2foYf#-`! z99*o++EN1FzoUs(k_j!lvii^lW(UAiBjU~61$l-eOGBgRbTA$x=zFN(>+Nqi$N#GF zJ0^+iJ>e+3Op{a)hymT5F{!@HUP9CjM?Kd!>6CtSl}c&RjEvwTo~M46F28;Lsjwpc z8Frp-8Lwwos^H>BwpV=*pwb&E)GIP5F8)7Wsr|j$m#TlKI$Qbqed~X{(mul)&g7Kk zrlWTjFi0H}I%_ISYITS`Q%hH%{=QW|t*7S@4%1J+0K#FkrKu(5rt$F3Yi%ia@BfZH zPNE+goGEN+<*6o$_}%di=8BVykK4i9?FAmcpSsebF&>f&%M8E^u96m80c49_+W?g! zGdIzm=fTsBrSS(R85tZlHG6mqeP-^Oaugl}3`zD)njfs~42*?lZg=@TvHQXH9H$tK z=Zx?@y(ylDg6s5O&Y30T@yk+qUO)#`wa=0Q_WM&2rLO}3{6G;+Xgp>#08zYz_g45* zKq`Hy^KN!CeDj%YU;+Li@-16uoNa~LKoJ4VQUYZPPowh75XTl0XZjbRtL?B+&`m0@ z6N2}F;Ns5zs`NkpRjpb5Z>pcEtma&$7xq2Pd)upKPn#JMUde4S1lh})P>(1t!YCGRBxSx}vC?;84Is*z7wP6lchT3_ zkt$;XU4fB2jA1==Bw-NsDvdWF99=pactB&leO9yD*jK&Ou!cjWl~iMcLJM-I{enGm zYP@MmTZ17oazW0n^kiIZ3GVu(yQ00sqo*1f$?*hwBP%42tX%7S9taNiCEjOHBys0| zt5Sco_TSeIRsXBX->Yn0*xAnisg<_mO{a3C>=J?3P>tiKaO(^%yls;DFhK1@@boT;!G%S41E?w9*qwJB3{se`@8WR4?UK{ z_dYy|pFTF3TNYD@2jq0U!fyOhThiw66Ehsm^!XeBaD0)cIw75=3{;&%XmLfPzqY!j zZriN7HieGy+>*RPHinFo5=Lv>()52`hUOCzMjMk0s%)U68QfTHDDvv2u}v^MUz%** zjic{3`A&Ps7I{>UUeo-|13FAwy~Y@P-*>IM{1#>7t`8dEfX^?)zYm5-Ba}YEJ5SVLxS1OBLpl zA$72WV~Z7}qj@|RVr{5|ODH{UlEvQMD!nrB6!i|Fp5*ej)YhkSL{XCQuQ{4J;&=oi zf3hxS?}^IHq7TtTiFXQIWf0dW|97DBXDapUYCl>1pR3dS_WSeK>mAbupU7w~ly*Q0 z{w-k)e0SSg{ zVIJPE8qvA?N#Av9K@F*_WR|b+SQQ*?&jDl!cv+d9_xuZO-i6YM@o(hk^<1YaA1_Ca zZ{+O&%Rr4pWcP(^%mSM82Fq)6B?nWX7PA?ulfKcBsQ64{Hv|4p8~D*M=3Z24b=LaK z20wmK1pR@@zBi-r0CN@UB>Tz+GGG>%lI@{)OT29@pbYI`<^Lb3e6>>hi{Sr1PW1oP zckcK%FX~8xJFCVyVcv@aSrjx#|G=1ZH__R`oZAH>g5FemM~Oc5FZtt&*zZVm?_XQ9~ z7FJ(aGyVC}&!1w)m+? zo#D;;ncF?oIbbWDBY>W;!sF19L8RR+ykc}Gd`uyc{O^zsU@}Li^-3v0Gf3hy&myKG zr&Z?|4z#2M?Vua(hvIJl146*8Osu|ng(?&i9^yN2)U@ZMNF2`?{C^c#xZq0X|DwuI zsQzb{1`|K=&Yk{!{Y$&69Vu1MG=A{5@kbzSc`=mCzti5+^qpEbx5iy=`qKtM@rACl z^D1d6T;P}R2seG>XQP@^8l7LwFF98UC zsdJg#v-^$0259IjYa5m&i+3Oh6dh=SigWYf&ZXJbE^$qiq#WpYE8$Ht8ahA$)$`E` z$B8_q)jP>Vm!;#~X%aj+P=>1xF2p_(UH3HOk-c7kcXwP2<9O>t5+56&hQRr@ubyafsnYp?zkN~ z0cF#Y1Gh^MNGwfHzgg`_czJpvLr@@jQ4Q0VElpTJBg!g^3@`fU^>J)buLi zj;D*(cndG#tIHax$zQ;JWOUduM5aX``v9{Ngag97`PvzxV|KlZWU(S!{9Yk%p){c> z#d(f0g0d}kVEDgWgY%HTV_&-RHlb|#XBKA(c=|Pkp1~AEjX0%Sh#VTnh&I92=9aD= zRATpgY{xsCatfWzz$t2U)azktABs<7@!`Q@pXj<(0!6JSJeGNRN39>GCNdHz3D53n z7acfmRnYsj;EZcvm3&H=R24OEz)R!rO0#2y7J&un@c)%ceZKamYDcO+L9n$l?hs$_ z-#0(l`JgkC`^CpMxX1gAY;me(D>s=Fvj^+TOXhbElFCYZi}%nUOZO(d^`t_X}!@=7leID3t|R5!cj0} z99vemm@O>~%6I^{59HqhA>tz)bv{6-C_}vY@Tf%4nC{;`Lx}?j%Bi6_?wv~jm&T{$ zb5Uc@w!cb?nKi!TVvZTx=+5Nv+>yJ4PZHsO7gt`T{|EZt^VL6D{WxE|JHMLOcRng> z&}e>W{2ovH_z>f?LnNvJo6`awuKkqMVnTVfw0NrPE_QeGqn+#ADBruEQHGcRD?l6~ z`AkxBx~>^Jdx<~ezE%#{oEe}WHPHbWBex5{jD}5VgwPAQQv#-ZT}QnQ*j^bUOq#BQ z;DkUZn@e5sz{?`J^_i%~vGub*I6&}>07_oe`LHZRPBCA?;W@%E!Gu*WrxMA>@C|MBr(5)R7=dK_xk|5eh2h z=OQw9hqs;1|L6Q~)qb`5k1N0WuATl$<&Dk_dgaD* zuulxz&8U83q7GW1tCk}1`?A6Mq0a7QosaXrR2pPNUkUH?JeqW?CyU{rzVi=(n(GGS zD}jkj#FkpNP))B=&{_!X+&s{$6ke3|diJnM(C5XC0SF%14Zvi2xrM+n$AAk$%A z<@pfz#T(A;jv5q9wO^|(S2yYZ{pk5# z|INwHr)^*5D6}LDdw=5R;E^yb8eki5QQPJ;`tbmDfrCeYP6{9WB{T`eYP5VlRJ>K!o=q9N;I8bg z79M!z$(ghy-X*N1H z+NQ`53{Bp;V>iY8j=G(48m;!JJUakU!8_UPsEfi>j)q#oHp?1u@(J%2;jZ8blYMm+ zN2PNi=(VUP*ZqI6^07+&Z0%pxo~{0s>a*1Ueyp$n&3iky>S#Bb8Pvwau%27R4DtRq zW2Z=ME+{m&Iy5=LN^D4FPwPi#^oRh%-x|aTuT33L`yii3tk|;Kk~WGa6s!fHEtJY{ zb)@t?)5uFfv2B(Y9^PD9Kg3$C!>wC5L}vOyFSIK7i|xj(6N-A{#}ZLN&-V%IH#^ew zp3dp$l-yH^o=(Y~Bt#vA(cf#Yc5bmb>jWYU!Sg1lIWsd`_+a+9JFQwV8X16tlkSDsZphqmV19aG-k$F)C% z`#&`NAofrhQV4_I+fo0EV>#TPk>2Jg^jC67LVjUh2@1viAR+zbD~hjNH;D{c)1}&XNRoOkzjwAjK0h@;7u$ zZhWHo@$v3M;_-)zWQ5$Z5EVF|x2O`xk`s)FNqthkt8uf|k!1Mv#Mj1;GI{bIGeQnY zNqLq^D^sxwsw@L-Jw`kNf2cePZ7Dxl+nqaX5E<$$Ndq`Q6pp!d@pJJlxXr?C{=San zzo#c&AOH5r2OkxhFbm`odH`YgQPjb&FV#lPZ?l@35*0~pZ=Hz`80iNjQi8xn^TQn} zfrHLmzK>za(~mAk^XmX+pji5G#&)paXl$>L34MV!Lj%J@^f_ds-1(>f?^kNSRJ*wP zLzVW(?!T4FTE_`+KQR7n68^xDs6`S5@w&nhg(({R<=KujxarQGffFVdcA9RXgh>my zMXHB42V@z3HGu{DoLUr@^s>DB1Cz!B^_8yN`%2p@?TW_3sv6%@xFdn}`N=5@x8xlx z!Q9=DVzBCa$orfBJ;)Uqd`akoJ~8xaN8;P>gbG~0kOp_KuXK5e3fXvyIj>wz9(`&) z-?`6Cabx_lCN0O~N6(M{-Cwt+;S_ei6@o1TIAyc zF8lG$JxnSzuIt86YU0xH?xz)N$QusY(;`-TBk&T+q_{dR|84yTrF6bEi_Ip0_PL38 zkg}Ks#nIt^uT|>H#Q$Hd{#50)ckj~Q{Y>YuP0(APyWj~bD)sF+=IknMOc7BXPQ%1` z%C}<}0vrWOkG3C$Hp;2054gVY6d$*RtqMc@Ts9-AzGVw#wj?aZPhM~VPP9^VJ14Fv zP@_YghuwC{D3wiYr=jX4Rv6^-Y|5&t5T>6w1$ZCRTdq7}L0y3oeO>1vjkwYL!1(n| zToxCCH*#Mafk6fudVa|=knmM*&H=#7od>ZDQ#ss^#DorGenj&DmzY@lws4VM*|B~Z zIf_H+eAfku4#i{QW>vu2=l@--`2SP2-&wn?dRyhci=zKZrFm`Vah8zOYJL0?9v+oi zJ)g^g0~M2(qrqDTrNVi6vGbVLFDJrJY_BB6bl{n24~8i2WXB}C8@oAI!;Wb$Cnj2abJU{3EKT@f`S^MjB0Di0TFDs9np9j#qrSp`VnT&GBku!4~vGVPC zVsPzMi7B`Yd4}Az${=*FJ60J&Y&3d_U)h8_f+SA!vd)ucHnzu`v;+)&;tWogs=d~% zwJYxb3p6leFz1GGvj=$>6P3c##+A1K#(hia1!x=uwiQg`dploX zLZ=(Aj-SwEd?t8Ao`=uyn{by2eL|5o8vN#hBCDu5I&8Q3!9f8C1jy$4&NQsbVh-I@PW@4d5{zk{dzb7(UCyzWj7N3Bh+ZwJD(7N>4 z;xaJvrc^?be(`OLAWvVP3anrX6E8#;3~9 zB_0eEkLDenW6TgF@eJ%F?f(%Hj%Zt3unf+(w?$+w0kCjf3-k0q=NY%K$HpJ;q`h>N z4?4ZFFjk8zxdKv-S~mX72u~K?x@Jkb7-MWp@hz50SuMw5W~-BqS$KI?YFP0GR~87T zJJ^AGM{?>jjSNh5#H0^Fj{+IP=N<;Z01yc3Y>QID_jjJwEKcM&(#hii4uyCWHqvHG zqKuq)$9_F$a)JtK=NC??@3-bznE*KlrRgD81b?iQ|EK@wkJbKptycYJlz*(2=C30be_{eY&0`! zdK2E(Oi)la2^N>n7K)5XGHR!1D4+yvRn8nx$J6IhhH|a%FO84PEr!D1PRkuBsvpbg z{WsDhc+`=U-Rzbghg}l*+rR%0RDOr}fB&dC|KFEk0#-UEESsa-m~dxY?#y{}MdROSpy+GpA`M{jWu1Au&R^WeEtOE; zghES}*HK0by(c48#>as1kmWAKYp&@?RF?QMqn0Uo49>?qaV+}`;Q6)E(5zieptWsu zq;fvp`20R*>csZOsU;k9-jwkx>Lfi{qV+{3q?{N%naEGQ4uaKue@9BTt- z_xBjC01k-G$&iY=QyXjijcTQqfs}75O_d}(3V5nn?MTagKfS;T`EV8Pz9g7i0;%Lw zoyZnSH%O_nfCGbI$qJ8ss&iT!s?l5+Z_pA)XRnwUhhzw;Kz#}n&--F#rBs5l20(?A zS7gm`riL^&bi9z#i@-*O3Ka4yS9TV)x;fhPBUiUbdP^7G6-#UXE1)*T^i7JRWD9YR zTvM%qF_Hhf!(Xco|HuDdK>zzx_0Q4+^m`@(KtIUTW^?Ms5{Bf&8I<8CXQif^%(D2_ zEu}faaAPJz8tVGax(r5E2ijzW{J59_9J`|BSWu=AHD40(4siF?L2)I&Xi5U(g+vUF z5{f_=<@-CPLYtMUO-z?Hp6DB0=r&*uZalx}M#&tz!)ojSQ z3qHNaiwqd{T{dkQU3&{CFW^>o`_e1+fteW zHGn9{UCnDcUa>tRXPoesW6wx|oT^4GPb|#TIA^XxGT>6Cu%>X90;&?+AZA@ZnsfXg z@W1D3f4X*6b!8m-SE=mY(~%tObmOf&DcIqf%ONRCb-W2&>Uy--+2KN1+i}_K@Gc?S z-S4$(s4;AxCGAhuvOxV_TfQG+fl)tW9$+wU{KKDz|87FUKUKc&G9}U(o)N*Q22Fy$CP3Bmy_D-9IbEY za-B20_ZHA$dlOlUbC$Yu{-N}O@CleL%Mp}DN%#-}r-wRQ&fH}Plq4gYXPCx?4Wk}x z!i`)8?Dr||MfFru8L?`KT`fKLsfbj4p+l{kt-dMP&hqcj(KkBK60zoP4bJstolQH@ z7oO2g%aXI33~3#SF;W77DtWQaH+43cK{RbMn13U0`kaQ5Ls~cNr6>165mjq|{zmuz zMU_*P`fsTI&Z>U%j{l;7{uQ^KG62`)b}IDu98FKDX|@izjYx{NVA0Pl&n{Un7S*bg zi{FxsJoMPcl1m8QWeGtb*TBpAKF)}-mJP~c&|(ljHGMiM|!eJ zEVQ%t-cToCiUs}RytUYop7?a**WZwFRi#?wjSg8Yu*UucZ^T6_uBa7T;q|7MB(_8` ziLge!qa|YXC$fD`3($Qle>6L1D!_zf`b!Ip`2Q)Yp)pCAX7ng!aF?h~)ITM)CNVe6 z(O}i!Sxea5-3DHmR|P-fUhemAoy4KLHn*Ufs6(ID#+e!-%GJ}188@176@PB!G;+aI zQu}~|R=I(>IM7*B=GjS_>idmXA%S*#_4TDKXLM11ks;hHSuj=_55aH2mpVIUz@{_S zc{rzRbbqMWbRz!MC)Xd~ysuRk`Ky8a{dV;~uKu>_@2Iw_|3&q` ztp1_uAFKWk)t{~Y`Re~t{l)74R{eX`|Finfs{f|?tF>BfqIN~?{k3arleJIQZm!*4 zySMgW?a|s7YR}ZZSo>=2RP9V{wYF7zrS|J;zrOYxYrnbnpVWR=tyBA{+8?O>k=oDH z{>R$Sp*{LnYJa2lcWVCt4U&IR`@d`du6|MdlKPePtLq=BUtj-J{ZReR`u+8X>yOo+ ztUp`-QvK`o`T7g>_4-To*Xq0VAFBUo{m1M7S^Yn+_v*i|{s-$nUH{|tKUM#;^`Ec* z)%xG6|K0jOtpAhxzpDS+ciga@*FRYQXzLB;_+xD*ygu2!gwKz+ujKRk_BDLo(7uk( zPqeS+^Tsy)m+PNwe}>PS+PCugsrKD`e!Bg6K0nijpH=^C`*A*RZqM*}OZym~huUA_ z^Ky&c@Z`hA`E z@p*segM2>F`6!=1(7A!n&v!n}=Yt)M%;zH=jpRt@DLxEy*?&-vy#^*C#jp0~VV|cc! zIXT|d7*2FGhUdDUnch`=F7-ad=LWG5A*qA?=e0%dQbAX*#l3jZ}m>{x!uzkUh2*B zxzl@r&zE}|!?~X3$jkmAn^Ub$E!soAhTRWur_NVyV zeft(Z-+Ei~@Plvb>EC)=Pyh9A>*>GYZ9V;m-qsBK@Y|Y!Z@>LDK7ZtG?Tz2~w)Vz< z^!6$XU%9yQC04)It4&sabG5=3@4&C-C%TeO2Jg(dx`vyy6EekXr<}-=?h-9uSo4>3-e5%2IU`ECOT)5Mx|-GKgBw=YyZ)~U;%Ztc zjRtmF#CxrEUe{>$#iBUDV;myQEsE1Nt9m+1qB&K9If{hN0@=nMnrZkI?3wp;Ub8vL zAm1h(G>B8Wqlt(0-Yb+f4C);hf7KW4iHD@?x@eryJkoiU4Gs)^^wVxqfx?+%sE znnc;aFeQ-{PT(b7*TNx#b&;^sN%iDZLWF6BF#s#OvLIX8kj`)#=STUfCj^Qux2rXqNOpuP5@VeT&Hc*~;&$ z5Mji>niqE^**!I}G~Na$vaiMMM76}0m#GFc5*mbka-cl3i(%skmF9|S>L_u4efPa? z`SXtU@ay16zsFX~q=QE46i(~x%Fw#PUtwH(`i(5i-&7hU*(Kc7!0M}Yuhf#} zw4_WEeh<%C?glEW*v6VCRT|lSwaiV%=Cq`FyskPY{mlKRyYF#kFhkogy|ZKnwUL<+ zkp;N=f~4F->Ow;@-$@Kxl3X3Kf`_e<8k^t4o}7`=lrh+T61w?#_X@YL85#bhF)Xs; zc0KPc1-JX$1!h`bYd%UmqSOM@NvVudMlg;}pDDP?K17mX_gAp$lFE?^U>agE1%u<} zt{PWRCzvtsBkw)Jm1$)~H0#{vxc;FRbD_Lm2)o;!EjpBKo~3Qo0{e#@15;@0OzHkt zHgnFXuCA;%zox`n@Or<%cgq>>a$Mqg^R`@B6}sfp5tnV0-0{7!H5pSqw}?zoN(z#txLQq~F}y zj`W2^e9$Xwt}jukThuUYuade@FA>2h<|LqTf<_E#&5>aySBZ@XLNA0^SLu3aWB^X- z>p|)B0e+W_5Vo9akl>Jm?Nwrf!SiowZE^nZK;^%#)W2E#Df<6?n%{nJ|Jv?eXNNo^ zB1{;VlXRb$-8>`G_>NbsnVbbknT5E2=r|k0B*5-g6LA#zi!!+DyHeSPg_hA$Ghtq) zl#CVfTmMM+L$)Bz_l&=llCI4OLA8TaST`uy_5N0JRe*b>%1Y)4-UKd--!Sg}e^C9u ze-8f7S1W(3@?gFJXkOdBfwy}H@u}NGO z2OquOz21D0ljCPTabOQm67J1gQDUg!(s?cGaK<}Ug;hmt)x4uCy=`XauJI!n=?i+G zN9WL0O~@xN(A`mrW12f_X3u=#8>ra-7Ai$)%Nq|H1O>)pkh-|7JE<+(Xl6h+$t?@& z1|R~(ING?&7=n4<&YV*gWGw{ODsBJ-@aN6pDNHp8cS+phR_{G=DI-qk_s$ch%PdXeUDuBW;7%`pjd|S6M%A zX5~)d$HC!NhiVP)%);`z9S#2-A|){M7L=KpE)CYse}Gin`Mb{j^Xjg2zJb=N3|97{uGGC} z8s8W{u(VTk!b@Q=rarY0e|c5?H$8NnvWW8KdNF0DwYPM!ewsucsU z(d8!y2rR!a)ij}Nee-?Y8*QRr9lze`i5^-MFc1qc8gG0#cIGUUM#UDy3A%GVW{~3J`^_qNhae?O-Bzh8v^|A(sgSAHyY z{@;~9Z{FDboSVc9somu5bH40T0z+-rVCQiy&d`AsftPsZRH(y$9va{2O6PlKB98@~ z(56ZhBmzcnm7Yv!4iX*^#?N$bvE$zS-1vQ#2!e*RniQZI>X$dHk(ux#k#yzrc)WpB zg1$P*jl{u6;z*dFNYBw(RiR|R!^CFyX69)>y7mKX`&euxekuqC6x@(1xU4Ic@BLR& zJvmGE5Whs?;wrZp-2<#V9@YE?|K$B$iGR;D-W!p9>p#rE_vs8Y4|Ppudm^XDd-B?_`lq0s zpb)FymJM2$EMGQ3SyocmW_0oZi6TKQ)n2&z^8@pV^QSiz8t}2D-^XQa6>R zh0wiEg!$G&_jbEtzkTC)d!4vqk9hxgK%haTrJhbtbL<7>nzo&UjAi!_v_Q*a6Liwr zT)00oQIU7-2LTR1g-I+}x^Bbs?E~drY@xwN4gN&_ATRtU2j;~U1%yF5nU!~Lm;(Ce9%3Wj4K=YQaIgU2Bcf$S8DAY;Vf6%y>__tUeki9O`8-XpTA^iXtppUB!t;Ir%H`iBt9i zezymtcF%UzIc2KxgSU;}(uq7i3Wz>kxdRu7=k<|s*AE4K-i;Umlri#vwSH@qbgm?v zA+OaLYW?6TkR=3Qv!7VokeW5WU1fjwJ6fD3^X2LkR;e0B{L|wRIJPeR^U$O@?VCd8 zW1bGl1&J1kmst!$zft*t5o(I3+IUFWz{}V{oU1@D}T81$-O3kljelj_eEL|j^i3+F4CtJZD~>$+Q-WJ0l=LC zwQv9i`yIP?==k=k%f;WLg5A52arC6gt?nb7`>7o0EwQVQ>>&ZJqL^ciE(RV;KZ}rK zTJIjV$6k0GGLV19|DQ#m5%8?^rYui*)6MR~dSoM`msa9UM>K_uEtzR#hPvb3+q!Dp zG1GV}1ISMMchAE)_BlsOL(<8Y+shK{CMJf6jzvOtrbVj}1uqEtnOtGqpy?FKR$g6E zr7#;vfUIz}O7-7Pu$V@^To^KxtM%CtdrGFkQW84OGWGcP`F8(w|5^jW4jME$48>>M@2QeWt*{WmKIr8A@XdQ z9O}H}nDDq`+**D_MsDF;3ICHka$nm<=l?+Er>RP;{q5?nR3GQJ-xt5INK#IpYHW_b z*%FtnyJL$Ex^qh#R9xN6b`jUbweOPcR6KQpX*>!wIXa;t)bIm%;S65F3+S9#=;e_i|~JR&-D(pRINuW0c1lyPq9DYa;`p!!$gAuK@v= z8z}Ufn^Vt8jnVp1_*-1t*aSz1ORdBZ+o`<OScPn33E|-MeF{#A+m1(8^8IK5TLGnc3?c>7i`Sc)LS4g3wXm95ZaInVi-J z>1%r`)WwP`EY<@jdJ`NE)#~`c^pJqq>LZPfuIm7j;Tk3Pa}r#MoFf8P?k@kfyAOBO z;$y1u)>p=xv&1r$ih(Q^St9JVL_h8bwBCi62^i*ZPJ)#)*98?yq7~=9oWgBfl>4G6 zjejC2shHC)U8}7^w-Y=Myn4iL1Z$hKi|R@`3((_kIMz`&9MqU) z@Vx%YnS@>6<+!G+b|P5O3m27p4s~59>{~oag~2Z7bnX7clb1*C!ZSmq+>X^9Pl{rz zJTzcF;MQ>KUeSGq1>C==0xprMCpf;eCkK|=FIa%D?-8%MG+FQ^ARz>ceo5E5faKJh zr!6CHU^yvh3~mue9CcCp#FBWR>{c58@zPnLO3@VJVE1X+>7467J(?$caW5`_LKO!b zQF}mGgLhIY)+!A}0d3(Rx%mHE=#3O zp1@pUX{x<2@7@ESS}Shd;tDlQLu{fKG&GOTa@;!qx*a!Xtpf;M7+{K)wmW_bhcCow zmF{!Q-Sous<2{DtMw0*|-Whi>qa;?uF4@WF52Nn8k+2-95~c3;*|tl0<@0s{{xj@LEWhK=W55$|NfPC{q(@7GQ4|T00NTZptKNChpEM^M>g=aaFBIi|r28#NW<=CkU zaPcsSU;UsRz!6Gz(S{<$hwBum*gM^?vbkp(8Ie;O0v}-f;WpzO5z7wT@oH4Y$cYi+t-t?1B7Z-(Pb`TVUG4S$17NUL9|G(lc||1inGX#YNmQEeG(b z*$vW)0F>vr{!KdwGqg++M!Jgf4dV0fQbb`?F8=@JO8tu3@2-8Q`ooo9e)o>!yZEH$ zN4x6QLGjo%<7YpyH*~P10l`5!8wv#QBP6wOa}1_SCYp)XEL*dq>0Fc*=`Swswp#{*U<)`TBVtvky> zKGt|{{CGy1%E)Q91f9E?%HmArQ7Hf6@5<%wH+5C!bm&gFIz!OA(jt(;#fk*KX0@v> z9W#yHjD%_WkUl8|SotU>2LrS|Ugfrt9)KE#2jNM)ETk}9^iWk(yehaz z6c77{&$9|_;Ek2H_P(U^|36!)f28*9+I#8$``sKMsA6x0aZ9F z(kIYvWuF=j)-QU+d2vywXIm>Fi)8c?D%*D2R!T2qTI)p;_E7hXJLws@tHhqMEz3Pg zprG-+Gd^j&$APgFp8At^N%ypE@|=@DTq$vX=bp#{#*znwUh1kxMHYmZJ^-#cicY}l z+UhM@YG5zx(^XnHrJ?)R_Z8g*GdLMJ=8?PWeACSF!oZCquM@nnZ+7Rk;U{)8AfS;$ zdS+owl1xtVCM)|swEOQ<_MNX6Yw978Fk&UyqmU}35!U)KA*sWc9WfjNwWAY>26=(Qn$G>!9 zAOk7UpfQ5z9gPmO^~g-zk4ab_@WU2`{D!y(UBLsXKe%-)z9e>3^a)LO7G=oO0KEEl3w7M zQ#nT^;hjEW<)nwR2PepRD|4zC7<= z2zu^l)^bikde)wHO(3=wmLa{dy{y{ew6*Y25{f!!(`D1&#IGbuBLH+L2Cj5pvdR3~ z_;Zk$%oFVF0_`3lSR1V;_#Cpr0dGFr-PRB$GCF7^hA?mxRZ0>+jja<^?BCi|#|}~f z8Te`9=<~|PAW(~LTy%L{fZB_+IhVy{|026^N=dH~{0O>@k}*34eGk&z+T@9yJ!6KB z>@D=Uu3C0X=d|KWTIeTG+jD+4h}_yb)FD-F4~w-(ohbe8cgU;V4eiy&Q{y*Cdas@o z`6!sd6+35f_OG;BASC@F@n)l|wpRO8e4UmIkwth`L%&Bs#=L;wQI`gTt!4L={QsL! zRj>V}+SjY|m0zmdH01C%@9nMr6p2X5y!^`ORj_Z6l-t8y!4mJ)qP z<3|$zfm;Npz1lrz&&-g5NQ8f5OMbofM(I?U`eNua+Wcf!jaw*?$tYq^Z@S05Jk{1V z5wbKA#4<4bTNM-p$s%_;jH3{;ArjSGczKmyctR^Zy|oWa1SN4F(hM?OOAaUd>MZ+3`m%y#P=2 z2|A8X&LPl{))BWOIc4c!zLwnL>Df{^!f5U!ObEDAwO3^{(^)CIly0rZrco|qH<&t| zhB6NkR(2ETd=|sBQ<@TR9%d!rQ;QZ&faz)O~JfxP>?%UEg@ zveR-M)U4;B$s^!6ia`e|Ad42&Am_e0lI!p_3HW7zfh&3An9)czb)^f(W-EuJ+JUb6 zI80B>jeob~{(FK8UF8AvFA^==+9Yt`mOKo{-;&w4zbef#1HqvVxanuRZ|J6+Xx=h@ zBuOB52x@7ZL$aI5`N_HJ^ZTru!p)P8wJe=yraWk`f>zU6p}aG(l^~u`{_o<-3*!I$ z(b_%LS1SJxUwlV?H81Z;O8QtM<3x@GQwED<#`py%wijHo?Y>Frv?6`rx6Et339}GK z$FEb`LOdpYFw6T|yr5bx*=C(gRG(Hv6-_SVn10D%b}N_PcdVmcyxd7 zfV*HbCNI5udoLVpS~6EEM_a}%@HMQ9iuk8=f0Xv|G+>*s>{;3xZZ9vFmL94k4q?dk z9PFv*LzY-s(pb%)A?fy6UsSs|3M#wCYAJM6AW$+_M9Ss(|A6ZMepBs_q5uCozn>}q zn_ufmHhOAeH-kr<2(yo(?z*aW3SK(R3a){>&TN%L6t`DN?FId1yYQ$8u_}F~c#zj$ zqLhu>4oJ z!fB9ca;Tl}2|l|#31ba;hSv29qq(fD#x}2Rx6lmvpj7q8 zQ!|A9w{RctYT`v;N@$zA?j#)IgJAGNK(m-RYm1Q8@g(VSO6h-x5L1^hU?Vu%2Bc&| zkt|^)qPMnoBWx%Dd?15KjgF|(6%5}GTIk_v%PXwv**?@F!jL^Ff`GSZqo>1s$N z%qY@5M|N=;%A^<%3O^vp!@@O9RN|qntq`bSmepNEdfw;%m)q;5HzR(g_AA`x_x03j z0UMctCnjKyfpmc<1;EQjipAG5&Yt4!aMTL#o|#`pEXL)i`@D9RaQ0;~>zWv;gPq|r z+dKGRa9sTVnDoE?G5FsEADaCrO}~R9;RQu3<`6#nad3MlaXis1dTa%CTEpxr0u$D z;O(rNqqe|;nO*YI`{mI{#XTg3F6pVO!AwpOKsstUVGxKa|I{{Qc1sjn+yY!G24#_; zTQPPL-CIg)VsK2Tyl>We?`Jgo>#Ug2kK7{|AJ2*5Cc%qx=l@6OR$BXo+Bd8Jy!wU8 zkDTx6e}}%m`9SYu94$c8nf*9)L!cA0034Kre1ef(IY~v5ONK^)p7wW?;09+aoiWS; zNNNa)n>X|%`-}sV5si%8?2oDFbwD~BW3CZx&QT-`$J1fHcK)p3!-*Qj=6Q&z^1h?U&jKeVVPa!qGwsi}4Dt5N?IP#MCOdk2%=zT;6pmEpuB^@~@ zM7la(1ck(*gAICc$9OX9foB|enN$=WRE0hjf zj)PMwO64|_M}v|Zz+YOlCLRp|9$gmw&7kvtpz=fF|NqJAAFBLYe*0bdwbuKDw&g?) zX)gtkJ5pq*hH`AlLnqlY;@VTXf;Xyl2%F^Y-VM&&?CVw>N?F)X2Yc7ZuN)*Qn}E;n zchK&oz3Uk{a^Dxu7&M}(!9{=+dwSxKO@?Ty*huR80GR+7M#!|TUx5&*AGgweQG zJ+)cj-uUo%Ba=Q+rF_1(`rbiguyVGI%7pO=3cPS3^s*x16H!w3i`dt|Fv*1$w3PSt zCUx!`8Szir`?x^J^nU-OFSiA{8Rdx;){1F?t&++Nk){WLo- zfl|b#%@`&jB0bYn8-nRZ2KzCI2TXIbo6Dh@F3p(4BMO1T-HUo^Jpf1c#Q-K6LcD6a%#fQ5xKN`)LrGiSFz3?xSv#^I+@SM+Q1$=6xqhJbCjS3! z^`h^#0>Da7!pKuuzQAxk{IEtYr8$6&#WoIfu-@nNyc}iB1n7FgSZ7=f(#j`^4X6$U zj%#C9EDbnJ3#F$kXebmImwT>{z`mqZpOHX>t|oPSJG5~Dx*@DXm_SHG~aZLQvs+Gj{^4rZ!nCa z(NnX4sfo4mHf*272&?T@#1s#P%wS(K_H0j$0973tIfN5cRQ1sQA?V@Rfnectl94x5E_m{xqc58BDE8o*K-d{ zLbuUxf-CA%dTc7Wqa5mZ1t|vr3G=JW3_nj4sxkC%T?WRr-V^vV z<{}tAAlF^qC;yj^&TpLT9fC6hqf^O(&-iTi?qq+>H1ec+5?sW`!Ai+8ZLGZ@vZkZ@ zE6)_|GgV`dXYw)2a?feFXW#&dr5xDR2FApX<2uHz4MFyS-t9U?jf`HbdC!xD^Q0%? z$$aJ9veQ!GD!n%wfmaVv=0|$!OF-J=;P|~T(%wFTeRoZZ&E<87xYHd|EwEQ1awb=U>S)1&rK2MZ;bL zhfAA83M~E5@UuPXsZ*VtfyyPe>p(*vMMa_sJR(!T9&5R6Tc42a?*4j3(sAMqt|W^|QxOr$_8 zFRxSSg}dgqSJg_AnmXJfxH&qk`f(ws;Jhn9_g0e#S*~oxMKJ24h_k(WbaWfL?;Zb| zBabeC?rGpT=f0;S*R@ldw4eu3VCxXWuDFzfrelvfG4*~xGPw70`B*FmMFbRh7GOe+ z!06Wg@~jEBMdzOk4e?uy|6N@9e5LlQwU?@YwEAS_Pw_?WuV%gXunx>bMx{+Ura#W> zn#O6toZDQUr3<^tEA)n5<|y8e>by--K1A&o^&XP3ʕoSv}LxJe5Rm7~}K>^RXj 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

;IwtAM5|6{@)uHG%jjf-ngQ1P2;-8 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 literal 0 HcmV?d00001 From 3b23045e0a01a0849a8f5bc06372ba4f9b010ae1 Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Wed, 28 May 2014 15:42:12 +0200 Subject: [PATCH 32/94] Fix tmp path in message tester --- tester/message_tester.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tester/message_tester.c b/tester/message_tester.c index b5b8934e4..09872e565 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -462,7 +462,7 @@ static void message_storage_migration() { // the messages.db has 10000 dummy messages with the very first DB scheme. // This will test the migration procedure - linphone_core_set_chat_database_path(marie->lc, "tmp.db"); + linphone_core_set_chat_database_path(marie->lc, tmp_db); MSList* chatrooms = linphone_core_get_chat_rooms(marie->lc); CU_ASSERT(ms_list_size(chatrooms) > 0); From 1293b195bd60bf23619ce86e97807c1c70f84c3e Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Wed, 28 May 2014 17:06:58 +0200 Subject: [PATCH 33/94] Add API to change the file prefix programatically in linphone tester --- tester/liblinphone_tester.h | 17 +++++++++-------- tester/tester.c | 13 +++++++++---- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index 5e5767bab..152dd6dd3 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -1,11 +1,11 @@ /* - liblinphone_tester - liblinphone test suite - Copyright (C) 2013 Belledonne Communications SARL + liblinphone_tester - liblinphone test suite + Copyright (C) 2013 Belledonne Communications SARL - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -67,6 +67,7 @@ extern int liblinphone_tester_test_index(const char *suite_name, const char *tes extern void liblinphone_tester_init(void); extern void liblinphone_tester_uninit(void); extern int liblinphone_tester_run_tests(const char *suite_name, const char *test_name); +extern void liblinphone_tester_set_fileprefix(const char* file_prefix); #ifdef __cplusplus }; @@ -177,7 +178,7 @@ typedef struct _stats { int number_of_LinphonePublishExpiring; int number_of_LinphonePublishError; int number_of_LinphonePublishCleared; - + int number_of_LinphoneConfiguringSkipped; int number_of_LinphoneConfiguringFailed; int number_of_LinphoneConfiguringSuccessful; @@ -230,7 +231,7 @@ const char *liblinphone_tester_get_notify_content(void); void liblinphone_tester_chat_message_state_change(LinphoneChatMessage* msg,LinphoneChatMessageState state,void* ud); void liblinphone_tester_check_rtcp(LinphoneCoreManager* caller, LinphoneCoreManager* callee); void liblinphone_tester_clock_start(MSTimeSpec *start); -bool_t liblinphone_tester_clock_elapsed(const MSTimeSpec *start, int value_ms); +bool_t liblinphone_tester_clock_elapsed(const MSTimeSpec *start, int value_ms); #endif /* LIBLINPHONE_TESTER_H_ */ diff --git a/tester/tester.c b/tester/tester.c index 3623073e4..3e09be635 100644 --- a/tester/tester.c +++ b/tester/tester.c @@ -80,8 +80,8 @@ LinphoneAddress * create_linphone_address(const char * domain) { static void auth_info_requested(LinphoneCore *lc, const char *realm, const char *username, const char *domain) { stats* counters; ms_message("Auth info requested for user id [%s] at realm [%s]\n" - ,username - ,realm); + ,username + ,realm); counters = get_stats(lc); counters->number_of_auth_info_requested++; } @@ -147,7 +147,7 @@ bool_t wait_for(LinphoneCore* lc_1, LinphoneCore* lc_2,int* counter,int value) { bool_t wait_for_list(MSList* lcs,int* counter,int value,int timeout_ms) { MSList* iterator; MSTimeSpec start; - + liblinphone_tester_clock_start(&start); while ((counter==NULL || *counternext) { @@ -164,7 +164,7 @@ static void set_codec_enable(LinphoneCore* lc,const char* type,int rate,bool_t e MSList* codecs_it; PayloadType* pt; for (codecs_it=codecs;codecs_it!=NULL;codecs_it=codecs_it->next) { - linphone_core_enable_payload_type(lc,(PayloadType*)codecs_it->data,0); + linphone_core_enable_payload_type(lc,(PayloadType*)codecs_it->data,0); } if((pt = linphone_core_find_payload_type(lc,type,rate,1))) { linphone_core_enable_payload_type(lc,pt, enable); @@ -322,6 +322,11 @@ const char * liblinphone_tester_test_name(const char *suite_name, int test_index return test_suite[suite_index]->tests[test_index].name; } +void liblinphone_tester_set_fileprefix(const char* file_prefix){ + liblinphone_tester_file_prefix = file_prefix; +} + + void liblinphone_tester_init(void) { add_test_suite(&setup_test_suite); add_test_suite(®ister_test_suite); From c72ebb6f8deb41e4954e7fe4f5b1e71c28c281db Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Thu, 29 May 2014 12:31:22 +0200 Subject: [PATCH 34/94] update ms2 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 6ce0bcea9..f47343572 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 6ce0bcea96b6e91c856e9a9dbab5b7bf8720ba9f +Subproject commit f47343572409561009e38a241ef3fde113d596d7 From 1a2e24b14aa4af35739d2cd9b797089de3aa91fa Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Thu, 29 May 2014 14:54:09 +0200 Subject: [PATCH 35/94] update mediastreamer2 to fix big regression with vp8 and h264 fix printf formatter to be compatible with visual studio --- coreapi/message_storage.c | 2 +- mediastreamer2 | 2 +- oRTP | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index 86887c7b7..1975edb23 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -371,7 +371,7 @@ void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) { } static void _linphone_message_storage_profile(void*data,const char*statement, sqlite3_uint64 duration){ - ms_warning("SQL statement '%s' took %" PRId64 " microseconds", statement, duration / 1000 ); + ms_warning("SQL statement '%s' took %llu microseconds", statement, (unsigned long long)duration / 1000LL ); } static void linphone_message_storage_activate_debug(sqlite3* db, bool_t debug){ diff --git a/mediastreamer2 b/mediastreamer2 index f47343572..5db89bd64 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit f47343572409561009e38a241ef3fde113d596d7 +Subproject commit 5db89bd644eb5a692c4b570e06a199943ff6cb26 diff --git a/oRTP b/oRTP index 2f3ce6970..a6c420eed 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 2f3ce697093694165a31f024a3141cd89a95bc42 +Subproject commit a6c420eedd03c677f5560c3b37bf2b5684c6b0f9 From 00fa127aecc7579a397730700eccf3c057d8f9b9 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Fri, 30 May 2014 08:41:32 +0200 Subject: [PATCH 36/94] fix format specifier again --- coreapi/message_storage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index 1975edb23..81d076fc5 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -371,7 +371,7 @@ void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) { } static void _linphone_message_storage_profile(void*data,const char*statement, sqlite3_uint64 duration){ - ms_warning("SQL statement '%s' took %llu microseconds", statement, (unsigned long long)duration / 1000LL ); + ms_warning("SQL statement '%s' took %" PRIu64 " microseconds", statement, (uint64_t)(duration / 1000LL) ); } static void linphone_message_storage_activate_debug(sqlite3* db, bool_t debug){ From 8c2e4d9e807f699ca70362bfbc08a73fee17a306 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Fri, 30 May 2014 12:06:26 +0200 Subject: [PATCH 37/94] increment sdp session id when making a response to pause/resume request don't accept linphone_core_update_call() outside of StreamsRunning state. --- coreapi/callbacks.c | 4 ++++ coreapi/linphonecall.c | 5 +++++ coreapi/linphonecore.c | 19 +++++++++++-------- coreapi/private.h | 1 + 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index 94d7f17d2..cc981d9a6 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -460,6 +460,8 @@ static void call_accept_update(LinphoneCore *lc, LinphoneCall *call){ } 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.")); @@ -467,6 +469,8 @@ static void call_resumed(LinphoneCore *lc, LinphoneCall *call){ } 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) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 04023c386..3f473ce53 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -306,6 +306,11 @@ static void setup_rtcp_xr(LinphoneCall *call, SalMediaDescription *md) { } } +void linphone_call_increment_local_media_description(LinphoneCall *call){ + SalMediaDescription *md=call->localdesc; + md->session_ver++; +} + void linphone_call_make_local_media_description(LinphoneCore *lc, LinphoneCall *call){ MSList *l; PayloadType *pt; diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index a9e472388..95431b1f9 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -3153,23 +3153,28 @@ int linphone_core_update_call(LinphoneCore *lc, LinphoneCall *call, const Linpho #ifdef VIDEO_ENABLED bool_t has_video = FALSE; #endif + + if (call->state!=LinphoneCallStreamsRunning){ + ms_error("linphone_core_update_call() is not allowed in [%s] state",linphone_call_state_to_string(call->state)); + return -1; + } + if (params!=NULL){ linphone_call_set_state(call,LinphoneCallUpdating,"Updating call"); -#ifdef VIDEO_ENABLED +#if defined(VIDEO_ENABLED) && defined(BUILD_UPNP) has_video = call->params.has_video; // Video removing if((call->videostream != NULL) && !params->has_video) { -#ifdef BUILD_UPNP if(call->upnp_session != NULL) { if (linphone_core_update_upnp(lc, call)<0) { /* uPnP port mappings failed, proceed with the call anyway. */ linphone_call_delete_upnp_session(call); } } -#endif //BUILD_UPNP + } -#endif /* VIDEO_ENABLED */ +#endif /* defined(VIDEO_ENABLED) && defined(BUILD_UPNP) */ _linphone_call_params_copy(&call->params,params); err=linphone_call_prepare_ice(call,FALSE); @@ -3178,10 +3183,9 @@ int linphone_core_update_call(LinphoneCore *lc, LinphoneCall *call, const Linpho return 0; } -#ifdef VIDEO_ENABLED +#if defined(VIDEO_ENABLED) && defined(BUILD_UPNP) // Video adding if (!has_video && call->params.has_video) { -#ifdef BUILD_UPNP if(call->upnp_session != NULL) { ms_message("Defer call update to add uPnP port mappings"); video_stream_prepare_video(call->videostream); @@ -3192,9 +3196,8 @@ int linphone_core_update_call(LinphoneCore *lc, LinphoneCall *call, const Linpho return err; } } -#endif //BUILD_UPNP } -#endif //VIDEO_ENABLED +#endif //defined(VIDEO_ENABLED) && defined(BUILD_UPNP) err = linphone_core_start_update_call(lc, call); }else{ #ifdef VIDEO_ENABLED diff --git a/coreapi/private.h b/coreapi/private.h index a2fb3d9c2..5ebccdf35 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -742,6 +742,7 @@ int linphone_core_get_calls_nb(const LinphoneCore *lc); void linphone_core_set_state(LinphoneCore *lc, LinphoneGlobalState gstate, const char *message); void linphone_call_make_local_media_description(LinphoneCore *lc, LinphoneCall *call); +void linphone_call_increment_local_media_description(LinphoneCall *call); void linphone_core_update_streams(LinphoneCore *lc, LinphoneCall *call, SalMediaDescription *new_md); bool_t linphone_core_is_payload_type_usable_for_bandwidth(LinphoneCore *lc, const PayloadType *pt, int bandwidth_limit); From 7a85da0ff0070a3b7ff9b61a366d7d7a784407de Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Fri, 30 May 2014 16:32:16 +0200 Subject: [PATCH 38/94] fix local ref overflow in JNI, due to too many logs during linphone_core_new(). --- coreapi/linphonecore_jni.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index f6478c5fe..4b04cb387 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -121,7 +121,13 @@ static void linphone_android_ortp_log_handler(OrtpLogLevel lev, const char *fmt, } if (handler_obj){ JNIEnv *env=ms_get_jni_env(); - env->CallVoidMethod(handler_obj,loghandler_id,env->NewStringUTF(LogDomain),(jint)lev,env->NewStringUTF(levname),env->NewStringUTF(str),NULL); + jstring jdomain=env->NewStringUTF(LogDomain); + jstring jlevname=env->NewStringUTF(levname); + jstring jstr=env->NewStringUTF(str); + env->CallVoidMethod(handler_obj,loghandler_id,jdomain,(jint)lev,jlevname,jstr,NULL); + if (jdomain) env->DeleteLocalRef(jdomain); + if (jlevname) env->DeleteLocalRef(jlevname); + if (jstr) env->DeleteLocalRef(jstr); }else linphone_android_log_handler(prio, str); } From 92b20e62feaa5ac039a21efc4d1a7be74a768d67 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Sat, 31 May 2014 11:29:53 +0200 Subject: [PATCH 39/94] fix warning --- coreapi/linphonecore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 95431b1f9..7cbb5b955 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -3150,7 +3150,7 @@ int linphone_core_start_update_call(LinphoneCore *lc, LinphoneCall *call){ **/ int linphone_core_update_call(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallParams *params){ int err=0; -#ifdef VIDEO_ENABLED +#if defined(VIDEO_ENABLED) && defined(BUILD_UPNP) bool_t has_video = FALSE; #endif From 2110281d2e8208453e353895ca7634731450ed62 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 28 May 2014 11:18:35 +0200 Subject: [PATCH 40/94] Handle AVPF and SAVPF profiles. --- coreapi/bellesip_sal/sal_op_call.c | 2 +- coreapi/bellesip_sal/sal_sdp.c | 18 +++--- coreapi/callbacks.c | 40 +++++++------ coreapi/linphonecall.c | 90 +++++++++++++----------------- coreapi/linphonecore.c | 2 +- coreapi/misc.c | 7 +++ coreapi/offeranswer.c | 9 +-- coreapi/private.h | 3 + coreapi/sal.c | 35 ++++++++++++ include/sal/sal.h | 6 ++ 10 files changed, 132 insertions(+), 80 deletions(-) diff --git a/coreapi/bellesip_sal/sal_op_call.c b/coreapi/bellesip_sal/sal_op_call.c index 9a2474b37..2a433b7f6 100644 --- a/coreapi/bellesip_sal/sal_op_call.c +++ b/coreapi/bellesip_sal/sal_op_call.c @@ -70,7 +70,7 @@ static void sdp_process(SalOp *h){ strcpy(h->result->streams[i].rtcp_addr,h->base.remote_media->streams[i].rtcp_addr); h->result->streams[i].rtcp_port=h->base.remote_media->streams[i].rtcp_port; - if (h->result->streams[i].proto == SalProtoRtpSavp) { + if ((h->result->streams[i].proto == SalProtoRtpSavpf) || (h->result->streams[i].proto == SalProtoRtpSavp)) { h->result->streams[i].crypto[0] = h->base.remote_media->streams[i].crypto[0]; } } diff --git a/coreapi/bellesip_sal/sal_sdp.c b/coreapi/bellesip_sal/sal_sdp.c index 23d0d8be4..0cf94bed3 100644 --- a/coreapi/bellesip_sal/sal_sdp.c +++ b/coreapi/bellesip_sal/sal_sdp.c @@ -143,7 +143,7 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session if ( stream->bandwidth>0 ) belle_sdp_media_description_set_bandwidth ( media_desc,"AS",stream->bandwidth ); - if ( stream->proto == SalProtoRtpSavp ) { + if ((stream->proto == SalProtoRtpSavpf) || (stream->proto == SalProtoRtpSavp)) { /* add crypto lines */ for ( j=0; jproto=SalProtoOther; if ( proto ) { - if ( strcasecmp ( proto,"RTP/AVP" ) ==0 ) - stream->proto=SalProtoRtpAvp; - else if ( strcasecmp ( proto,"RTP/SAVP" ) ==0 ) { - stream->proto=SalProtoRtpSavp; - }else{ + if (strcasecmp(proto, "RTP/AVP") == 0) { + stream->proto = SalProtoRtpAvp; + } else if (strcasecmp(proto, "RTP/SAVP") == 0) { + stream->proto = SalProtoRtpSavp; + } else if (strcasecmp(proto, "RTP/AVPF") == 0) { + stream->proto = SalProtoRtpAvpf; + } else if (strcasecmp(proto, "RTP/SAVPF") == 0) { + stream->proto = SalProtoRtpSavpf; + } else { strncpy(stream->proto_other,proto,sizeof(stream->proto_other)-1); } } @@ -532,7 +536,7 @@ static SalStreamDescription * sdp_to_stream_description(SalMediaDescription *md, } /* Read crypto lines if any */ - if ( stream->proto == SalProtoRtpSavp ) { + if ((stream->proto == SalProtoRtpSavpf) || (stream->proto == SalProtoRtpSavp)) { sdp_parse_media_crypto_parameters(media_desc, stream); } diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index cc981d9a6..64f17f411 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -670,24 +670,32 @@ static void call_failure(SalOp *op){ break; case SalReasonUnsupportedContent: /*params.media_encryption == LinphoneMediaEncryptionSRTP && - !linphone_core_is_media_encryption_mandatory(lc)) { + ms_message("Outgoing call [%p] failed with SRTP and/or AVPF enabled", call); + if ((call->state == LinphoneCallOutgoingInit) + || (call->state == LinphoneCallOutgoingProgress) + || (call->state == LinphoneCallOutgoingRinging) /* Push notification case */ + || (call->state == LinphoneCallOutgoingEarlyMedia)) { int i; - ms_message("Outgoing call [%p] failed with SRTP (SAVP) enabled",call); - if (call->state==LinphoneCallOutgoingInit - || call->state==LinphoneCallOutgoingProgress - || call->state==LinphoneCallOutgoingRinging /*push case*/ - || call->state==LinphoneCallOutgoingEarlyMedia){ - ms_message("Retrying call [%p] with AVP",call); - /* clear SRTP local params */ - call->params.media_encryption = LinphoneMediaEncryptionNone; - for(i=0; ilocaldesc->n_active_streams; i++) { - call->localdesc->streams[i].proto = SalProtoRtpAvp; - memset(call->localdesc->streams[i].crypto, 0, sizeof(call->localdesc->streams[i].crypto)); + for (i = 0; i < call->localdesc->n_active_streams; i++) { + if (call->params.media_encryption == LinphoneMediaEncryptionSRTP) { + if (call->params.avpf_enabled == TRUE) { + if (i == 0) ms_message("Retrying call [%p] with SAVP", call); + call->params.avpf_enabled = FALSE; + linphone_core_restart_invite(lc, call); + return; + } else if (!linphone_core_is_media_encryption_mandatory(lc)) { + if (i == 0) ms_message("Retrying call [%p] with AVP", call); + call->params.media_encryption = LinphoneMediaEncryptionNone; + memset(call->localdesc->streams[i].crypto, 0, sizeof(call->localdesc->streams[i].crypto)); + linphone_core_restart_invite(lc, call); + return; + } + } else if (call->params.avpf_enabled == TRUE) { + if (i == 0) ms_message("Retrying call [%p] with AVP", call); + call->params.avpf_enabled = FALSE; + linphone_core_restart_invite(lc, call); + return; } - linphone_core_restart_invite(lc, call); - return; } } msg=_("Incompatible media parameters."); diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 3f473ce53..1a962ef3e 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -265,8 +265,8 @@ static void setup_encryption_keys(LinphoneCall *call, SalMediaDescription *md){ bool_t keep_srtp_keys=lp_config_get_int(lc->config,"sip","keep_srtp_keys",1); for(i=0; in_active_streams; i++) { - if (md->streams[i].proto == SalProtoRtpSavp) { - if (keep_srtp_keys && old_md && old_md->streams[i].proto==SalProtoRtpSavp){ + if (is_encryption_active(&md->streams[i]) == TRUE) { + if (keep_srtp_keys && old_md && is_encryption_active(&old_md->streams[i]) == TRUE){ int j; ms_message("Keeping same crypto keys."); for(j=0;jsession_ver++; } +static SalMediaProto get_proto_from_call_params(LinphoneCallParams *params) { + if ((params->media_encryption == LinphoneMediaEncryptionSRTP) && params->avpf_enabled) return SalProtoRtpSavpf; + if (params->media_encryption == LinphoneMediaEncryptionSRTP) return SalProtoRtpSavp; + if (params->avpf_enabled) return SalProtoRtpAvpf; + return SalProtoRtpAvp; +} + void linphone_call_make_local_media_description(LinphoneCore *lc, LinphoneCall *call){ MSList *l; PayloadType *pt; @@ -349,8 +356,7 @@ void linphone_call_make_local_media_description(LinphoneCore *lc, LinphoneCall * strncpy(md->streams[0].name,"Audio",sizeof(md->streams[0].name)-1); md->streams[0].rtp_port=call->media_ports[0].rtp_port; md->streams[0].rtcp_port=call->media_ports[0].rtcp_port; - md->streams[0].proto=(call->params.media_encryption == LinphoneMediaEncryptionSRTP) ? - SalProtoRtpSavp : SalProtoRtpAvp; + md->streams[0].proto=get_proto_from_call_params(&call->params); md->streams[0].type=SalAudio; if (call->params.down_ptime) md->streams[0].ptime=call->params.down_ptime; @@ -961,10 +967,6 @@ const LinphoneCallParams * linphone_call_get_current_params(LinphoneCall *call){ return &call->current_params; } -static bool_t is_video_active(const SalStreamDescription *sd){ - return sd->rtp_port!=0 && sd->dir!=SalStreamInactive; -} - /** * Returns call parameters proposed by remote. * @@ -976,19 +978,20 @@ const LinphoneCallParams * linphone_call_get_remote_params(LinphoneCall *call){ memset(cp,0,sizeof(*cp)); if (call->op){ SalMediaDescription *md=sal_call_get_remote_media_description(call->op); - if (md){ - SalStreamDescription *asd,*vsd,*secure_asd,*secure_vsd; + if (md) { + SalStreamDescription *sd; + unsigned int i; + unsigned int nb_audio_streams = sal_media_description_nb_active_streams_of_type(md, SalAudio); + unsigned int nb_video_streams = sal_media_description_nb_active_streams_of_type(md, SalVideo); - asd=sal_media_description_find_stream(md,SalProtoRtpAvp,SalAudio); - vsd=sal_media_description_find_stream(md,SalProtoRtpAvp,SalVideo); - secure_asd=sal_media_description_find_stream(md,SalProtoRtpSavp,SalAudio); - secure_vsd=sal_media_description_find_stream(md,SalProtoRtpSavp,SalVideo); - if (secure_vsd){ - cp->has_video=is_video_active(secure_vsd); - if (secure_asd || asd==NULL) - cp->media_encryption=LinphoneMediaEncryptionSRTP; - }else if (vsd){ - cp->has_video=is_video_active(vsd); + for (i = 0; i < nb_video_streams; i++) { + sd = sal_media_description_get_active_stream_of_type(md, SalVideo, i); + if (is_video_active(sd) == TRUE) cp->has_video = TRUE; + if (is_encryption_active(sd) == TRUE) cp->media_encryption = LinphoneMediaEncryptionSRTP; + } + for (i = 0; i < nb_audio_streams; i++) { + sd = sal_media_description_get_active_stream_of_type(md, SalAudio, i); + if (is_encryption_active(sd) == TRUE) cp->media_encryption = LinphoneMediaEncryptionSRTP; } if (!cp->has_video){ if (md->bandwidth>0 && md->bandwidth<=linphone_core_get_edge_bw(call->core)){ @@ -1861,12 +1864,10 @@ static void configure_rtp_session_for_rtcp_xr(LinphoneCore *lc, LinphoneCall *ca const SalStreamDescription *localstream; const SalStreamDescription *remotestream; - localstream = sal_media_description_find_stream(call->localdesc, SalProtoRtpSavp, type); - if (!localstream) localstream = sal_media_description_find_stream(call->localdesc, SalProtoRtpAvp, type); + localstream = sal_media_description_find_best_stream(call->localdesc, type); if (!localstream) return; localconfig = &localstream->rtcp_xr; - remotestream = sal_media_description_find_stream(sal_call_get_remote_media_description(call->op), SalProtoRtpSavp, type); - if (!remotestream) remotestream = sal_media_description_find_stream(sal_call_get_remote_media_description(call->op), SalProtoRtpAvp, type); + remotestream = sal_media_description_find_best_stream(sal_call_get_remote_media_description(call->op), type); if (!remotestream) return; remoteconfig = &remotestream->rtcp_xr; @@ -1902,14 +1903,8 @@ static void linphone_call_start_audio_stream(LinphoneCall *call, const char *cna int crypto_idx; snprintf(rtcp_tool,sizeof(rtcp_tool)-1,"%s-%s",linphone_core_get_user_agent_name(),linphone_core_get_user_agent_version()); - /* look for savp stream first */ - stream=sal_media_description_find_stream(call->resultdesc, - SalProtoRtpSavp,SalAudio); - /* no savp audio stream, use avp */ - if (!stream) - stream=sal_media_description_find_stream(call->resultdesc, - SalProtoRtpAvp,SalAudio); + stream = sal_media_description_find_best_stream(call->resultdesc, SalAudio); if (stream && stream->dir!=SalStreamInactive && stream->rtp_port!=0){ playcard=lc->sound_conf.lsd_card ? lc->sound_conf.lsd_card : lc->sound_conf.play_sndcard; @@ -1965,8 +1960,8 @@ static void linphone_call_start_audio_stream(LinphoneCall *call, const char *cna call->current_params.record_file=ms_strdup(call->params.record_file); } /* valid local tags are > 0 */ - if (stream->proto == SalProtoRtpSavp) { - local_st_desc=sal_media_description_find_stream(call->localdesc,SalProtoRtpSavp,SalAudio); + if (is_encryption_active(stream) == TRUE) { + local_st_desc=sal_media_description_find_stream(call->localdesc,stream->proto,SalAudio); crypto_idx = find_crypto_index_from_tag(local_st_desc->crypto, stream->crypto_local_tag); if (crypto_idx >= 0) { @@ -2020,17 +2015,10 @@ static void linphone_call_start_video_stream(LinphoneCall *call, const char *cna #ifdef VIDEO_ENABLED LinphoneCore *lc=call->core; int used_pt=-1; - - /* look for savp stream first */ - const SalStreamDescription *vstream=sal_media_description_find_stream(call->resultdesc, - SalProtoRtpSavp,SalVideo); char rtcp_tool[128]={0}; - snprintf(rtcp_tool,sizeof(rtcp_tool)-1,"%s-%s",linphone_core_get_user_agent_name(),linphone_core_get_user_agent_version()); + const SalStreamDescription *vstream; - /* no savp audio stream, use avp */ - if (!vstream) - vstream=sal_media_description_find_stream(call->resultdesc, - SalProtoRtpAvp,SalVideo); + snprintf(rtcp_tool,sizeof(rtcp_tool)-1,"%s-%s",linphone_core_get_user_agent_name(),linphone_core_get_user_agent_version()); /* shutdown preview */ if (lc->previewstream!=NULL) { @@ -2038,6 +2026,7 @@ static void linphone_call_start_video_stream(LinphoneCall *call, const char *cna lc->previewstream=NULL; } + vstream = sal_media_description_find_best_stream(call->resultdesc, SalVideo); if (vstream!=NULL && vstream->dir!=SalStreamInactive && vstream->rtp_port!=0) { const char *rtp_addr=vstream->rtp_addr[0]!='\0' ? vstream->rtp_addr : call->resultdesc->addr; const char *rtcp_addr=vstream->rtcp_addr[0]!='\0' ? vstream->rtcp_addr : call->resultdesc->addr; @@ -2088,7 +2077,7 @@ static void linphone_call_start_video_stream(LinphoneCall *call, const char *cna cam=get_nowebcam_device(); } if (!is_inactive){ - if (vstream->proto == SalProtoRtpSavp) { + if (is_encryption_active(vstream) == TRUE) { int crypto_idx = find_crypto_index_from_tag(local_st_desc->crypto, vstream->crypto_local_tag); if (crypto_idx >= 0) { media_stream_set_srtp_recv_key(&call->videostream->ms,vstream->crypto[0].algo,vstream->crypto[0].master_key); @@ -2121,8 +2110,7 @@ void linphone_call_start_media_streams(LinphoneCall *call, bool_t all_inputs_mut char *cname; bool_t use_arc=linphone_core_adaptive_rate_control_enabled(lc); #ifdef VIDEO_ENABLED - const SalStreamDescription *vstream=sal_media_description_find_stream(call->resultdesc, - SalProtoRtpAvp,SalVideo); + const SalStreamDescription *vstream=sal_media_description_find_best_stream(call->resultdesc,SalVideo); #endif call->current_params.audio_codec = NULL; @@ -2209,17 +2197,17 @@ void linphone_call_update_crypto_parameters(LinphoneCall *call, SalMediaDescript SalStreamDescription *new_stream; const SalStreamDescription *local_st_desc; - local_st_desc = sal_media_description_find_stream(call->localdesc, SalProtoRtpSavp, SalAudio); - old_stream = sal_media_description_find_stream(old_md, SalProtoRtpSavp, SalAudio); - new_stream = sal_media_description_find_stream(new_md, SalProtoRtpSavp, SalAudio); + local_st_desc = sal_media_description_find_secure_stream_of_type(call->localdesc, SalAudio); + old_stream = sal_media_description_find_secure_stream_of_type(old_md, SalAudio); + new_stream = sal_media_description_find_secure_stream_of_type(new_md, SalAudio); if (call->audiostream && local_st_desc && old_stream && new_stream && update_stream_crypto_params(call,local_st_desc,old_stream,new_stream,&call->audiostream->ms)){ } #ifdef VIDEO_ENABLED - local_st_desc = sal_media_description_find_stream(call->localdesc, SalProtoRtpSavp, SalVideo); - old_stream = sal_media_description_find_stream(old_md, SalProtoRtpSavp, SalVideo); - new_stream = sal_media_description_find_stream(new_md, SalProtoRtpSavp, SalVideo); + local_st_desc = sal_media_description_find_secure_stream_of_type(call->localdesc, SalVideo); + old_stream = sal_media_description_find_secure_stream_of_type(old_md, SalVideo); + new_stream = sal_media_description_find_secure_stream_of_type(new_md, SalVideo); if (call->videostream && local_st_desc && old_stream && new_stream && update_stream_crypto_params(call,local_st_desc,old_stream,new_stream,&call->videostream->ms)){ } diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 7cbb5b955..15f7945cf 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -2972,7 +2972,7 @@ bool_t linphone_core_media_description_has_srtp(const SalMediaDescription *md){ for(i=0;in_active_streams;i++){ const SalStreamDescription *sd=&md->streams[i]; - if (sd->proto!=SalProtoRtpSavp){ + if (is_encryption_active(sd) != TRUE){ return FALSE; } } diff --git a/coreapi/misc.c b/coreapi/misc.c index c27147107..f8fae4210 100644 --- a/coreapi/misc.c +++ b/coreapi/misc.c @@ -1520,3 +1520,10 @@ const MSCryptoSuite * linphone_core_get_srtp_crypto_suites(LinphoneCore *lc){ return result; } +bool_t is_video_active(const SalStreamDescription *sd) { + return (sd->rtp_port != 0) && (sd->dir != SalStreamInactive); +} + +bool_t is_encryption_active(const SalStreamDescription *sd) { + return ((sd->proto == SalProtoRtpSavpf) || (sd->proto == SalProtoRtpSavp)); +} diff --git a/coreapi/offeranswer.c b/coreapi/offeranswer.c index fd99f421c..be8998168 100644 --- a/coreapi/offeranswer.c +++ b/coreapi/offeranswer.c @@ -233,7 +233,7 @@ static void initiate_outgoing(const SalStreamDescription *local_offer, }else{ result->rtp_port=0; } - if (result->proto == SalProtoRtpSavp) { + if (is_encryption_active(result) == TRUE) { /* verify crypto algo */ memset(result->crypto, 0, sizeof(result->crypto)); if (!match_crypto_algo(local_offer->crypto, remote_answer->crypto, &result->crypto[0], &result->crypto_local_tag, FALSE)) @@ -259,7 +259,7 @@ static void initiate_incoming(const SalStreamDescription *local_cap, }else{ result->rtp_port=0; } - if (result->proto == SalProtoRtpSavp) { + if (is_encryption_active(result) == TRUE) { /* select crypto algo */ memset(result->crypto, 0, sizeof(result->crypto)); if (!match_crypto_algo(local_cap->crypto, remote_offer->crypto, &result->crypto[0], &result->crypto_local_tag, TRUE)) @@ -323,10 +323,11 @@ static bool_t local_stream_not_already_used(const SalMediaDescription *result, c return TRUE; } -/*in answering mode, we consider that if we are able to make SAVP, then we can do AVP as well*/ +/*in answering mode, we consider that if we are able to make AVPF/SAVP/SAVPF, then we can do AVP as well*/ static bool_t proto_compatible(SalMediaProto local, SalMediaProto remote){ if (local==remote) return TRUE; - if (remote==SalProtoRtpAvp && local==SalProtoRtpSavp) return TRUE; + if ((remote==SalProtoRtpAvp) && ((local==SalProtoRtpSavp) || (local==SalProtoRtpAvpf) || (local==SalProtoRtpSavpf))) + return TRUE; return FALSE; } diff --git a/coreapi/private.h b/coreapi/private.h index 5ebccdf35..dc9a02e80 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -95,6 +95,7 @@ struct _LinphoneCallParams{ char *session_name; SalCustomHeader *custom_headers; bool_t has_video; + bool_t avpf_enabled; /* RTCP feedback messages are enabled */ 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; @@ -389,6 +390,8 @@ bool_t linphone_core_rtcp_enabled(const LinphoneCore *lc); LinphoneCall * is_a_linphone_call(void *user_pointer); LinphoneProxyConfig * is_a_linphone_proxy_config(void *user_pointer); +bool_t is_video_active(const SalStreamDescription *sd); +bool_t is_encryption_active(const SalStreamDescription *sd); void linphone_core_queue_task(LinphoneCore *lc, belle_sip_source_func_t task_fun, void *data, const char *task_description); diff --git a/coreapi/sal.c b/coreapi/sal.c index cb94d675c..74d087139 100644 --- a/coreapi/sal.c +++ b/coreapi/sal.c @@ -91,6 +91,39 @@ SalStreamDescription *sal_media_description_find_stream(SalMediaDescription *md, return NULL; } +unsigned int sal_media_description_nb_active_streams_of_type(SalMediaDescription *md, SalStreamType type) { + unsigned int i; + unsigned int nb = 0; + for (i = 0; i < md->n_active_streams; ++i) { + if (md->streams[i].type == type) nb++; + } + return nb; +} + +SalStreamDescription * sal_media_description_get_active_stream_of_type(SalMediaDescription *md, SalStreamType type, unsigned int idx) { + unsigned int i; + for (i = 0; i < md->n_active_streams; ++i) { + if (md->streams[i].type == type) { + if (idx-- == 0) return &md->streams[i]; + } + } + return NULL; +} + +SalStreamDescription * sal_media_description_find_secure_stream_of_type(SalMediaDescription *md, SalStreamType type) { + SalStreamDescription *desc = sal_media_description_find_stream(md, SalProtoRtpSavpf, type); + if (desc == NULL) desc = sal_media_description_find_stream(md, SalProtoRtpSavp, type); + return desc; +} + +SalStreamDescription * sal_media_description_find_best_stream(SalMediaDescription *md, SalStreamType type) { + SalStreamDescription *desc = sal_media_description_find_stream(md, SalProtoRtpSavpf, type); + if (desc == NULL) desc = sal_media_description_find_stream(md, SalProtoRtpSavp, type); + if (desc == NULL) desc = sal_media_description_find_stream(md, SalProtoRtpAvpf, type); + if (desc == NULL) desc = sal_media_description_find_stream(md, SalProtoRtpAvp, type); + return desc; +} + bool_t sal_media_description_empty(const SalMediaDescription *md){ if (md->n_active_streams > 0) return FALSE; return TRUE; @@ -515,6 +548,8 @@ const char* sal_media_proto_to_string(SalMediaProto type) { switch (type) { case SalProtoRtpAvp:return "RTP/AVP"; case SalProtoRtpSavp:return "RTP/SAVP"; + case SalProtoRtpAvpf:return "RTP/AVPF"; + case SalProtoRtpSavpf:return "RTP/SAVPF"; default: return "unknown"; } } diff --git a/include/sal/sal.h b/include/sal/sal.h index fbeaf2e14..d5712c09d 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -123,6 +123,8 @@ const char* sal_stream_type_to_string(SalStreamType type); typedef enum{ SalProtoRtpAvp, SalProtoRtpSavp, + SalProtoRtpAvpf, + SalProtoRtpSavpf, SalProtoOther }SalMediaProto; const char* sal_media_proto_to_string(SalMediaProto type); @@ -251,6 +253,10 @@ int sal_media_description_equals(const SalMediaDescription *md1, const SalMediaD bool_t sal_media_description_has_dir(const SalMediaDescription *md, SalStreamDir dir); SalStreamDescription *sal_media_description_find_stream(SalMediaDescription *md, SalMediaProto proto, SalStreamType type); +unsigned int sal_media_description_nb_active_streams_of_type(SalMediaDescription *md, SalStreamType type); +SalStreamDescription * sal_media_description_get_active_stream_of_type(SalMediaDescription *md, SalStreamType type, unsigned int idx); +SalStreamDescription * sal_media_description_find_secure_stream_of_type(SalMediaDescription *md, SalStreamType type); +SalStreamDescription * sal_media_description_find_best_stream(SalMediaDescription *md, SalStreamType type); void sal_media_description_set_dir(SalMediaDescription *md, SalStreamDir stream_dir); From 1a5f37eabaf498eae98a76a08991cd1258c7fabd Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 28 May 2014 12:14:18 +0200 Subject: [PATCH 41/94] Allow activation of AVPF for a call based on the proxy configuration. --- coreapi/linphonecall.c | 1 + coreapi/linphonecore.c | 10 ++++++++-- coreapi/linphonecore.h | 21 +++++++++++++++++++++ coreapi/private.h | 5 ++++- coreapi/proxy.c | 18 ++++++++++++++++++ include/sal/sal.h | 1 + 6 files changed, 53 insertions(+), 3 deletions(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 1a962ef3e..84168419c 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -340,6 +340,7 @@ void linphone_call_make_local_media_description(LinphoneCore *lc, LinphoneCall * md->session_id=(old_md ? old_md->session_id : (rand() & 0xfff)); md->session_ver=(old_md ? (old_md->session_ver+1) : (rand() & 0xfff)); md->n_total_streams=(call->biggestdesc ? call->biggestdesc->n_total_streams : 1); + md->avpf_rr_interval = call->params.avpf_rr_interval; strncpy(md->addr,local_ip,sizeof(md->addr)); strncpy(md->username,linphone_address_get_username(addr),sizeof(md->username)); diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 15f7945cf..65b18e6e8 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -2825,6 +2825,7 @@ LinphoneCall * linphone_core_invite_address_with_params(LinphoneCore *lc, const char *real_url=NULL; LinphoneCall *call; bool_t defer = FALSE; + LinphoneCallParams *cp = linphone_call_params_copy(params); linphone_core_preempt_sound_resources(lc); @@ -2837,20 +2838,24 @@ LinphoneCall * linphone_core_invite_address_with_params(LinphoneCore *lc, const real_url=linphone_address_as_string(addr); proxy=linphone_core_lookup_known_proxy(lc,addr); - if (proxy!=NULL) + if (proxy!=NULL) { from=linphone_proxy_config_get_identity(proxy); + cp->avpf_enabled = proxy->avpf_enabled; + cp->avpf_rr_interval = proxy->avpf_rr_interval; + } /* if no proxy or no identity defined for this proxy, default to primary contact*/ if (from==NULL) from=linphone_core_get_primary_contact(lc); parsed_url2=linphone_address_new(from); - call=linphone_call_new_outgoing(lc,parsed_url2,linphone_address_clone(addr),params,proxy); + call=linphone_call_new_outgoing(lc,parsed_url2,linphone_address_clone(addr),cp,proxy); if(linphone_core_add_call(lc,call)!= 0) { ms_warning("we had a problem in adding the call into the invite ... weird"); linphone_call_unref(call); + linphone_call_params_destroy(cp); return NULL; } @@ -2895,6 +2900,7 @@ LinphoneCall * linphone_core_invite_address_with_params(LinphoneCore *lc, const if (defer==FALSE) linphone_core_start_invite(lc,call,NULL); if (real_url!=NULL) ms_free(real_url); + linphone_call_params_destroy(cp); return call; } diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index fc02cbb23..b9a199519 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -929,6 +929,27 @@ LINPHONE_PUBLIC void linphone_proxy_config_set_privacy(LinphoneProxyConfig *para * */ LINPHONE_PUBLIC LinphonePrivacyMask linphone_proxy_config_get_privacy(const LinphoneProxyConfig *params); +/** + * Indicates whether AVPF/SAVPF must be used for calls using this proxy config. + * @param[in] cfg #LinphoneProxyConfig object + * @param[in] enable True to enable AVPF/SAVF, false to disable it. + */ +LINPHONE_PUBLIC void linphone_proxy_config_enable_avpf(LinphoneProxyConfig *cfg, bool_t enable); + +/** + * Set the interval between regular RTCP reports when using AVPF/SAVPF. + * @param[in] cfg #LinphoneProxyConfig object + * @param[in] interval The interval in seconds (between 0 and 5 seconds). + */ +LINPHONE_PUBLIC void linphone_proxy_config_set_avpf_rr_interval(LinphoneProxyConfig *cfg, uint8_t interval); + +/** + * Get the interval between regular RTCP reports when using AVPF/SAVPF. + * @param[in] cfg #LinphoneProxyConfig object + * @return The interval in seconds. + */ +LINPHONE_PUBLIC uint8_t linphone_proxy_config_get_avpf_rr_interval(const LinphoneProxyConfig *cfg); + /** * @} **/ diff --git a/coreapi/private.h b/coreapi/private.h index dc9a02e80..589297849 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -100,6 +100,7 @@ struct _LinphoneCallParams{ bool_t in_conference; /*in conference mode */ bool_t low_bandwidth; LinphonePrivacyMask privacy; + uint8_t avpf_rr_interval; }; struct _LinphoneCallLog{ @@ -430,7 +431,9 @@ struct _LinphoneProxyConfig bool_t dial_escape_plus; bool_t send_publish; bool_t send_statistics; - bool_t pad[3]; + bool_t avpf_enabled; + bool_t pad; + uint8_t avpf_rr_interval; void* user_data; time_t deletion_date; LinphonePrivacyMask privacy; diff --git a/coreapi/proxy.c b/coreapi/proxy.c index ea021639a..278fd9be6 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -1183,6 +1183,8 @@ void linphone_proxy_config_write_to_config_file(LpConfig *config, LinphoneProxyC lp_config_set_int(config,key,"reg_expires",obj->expires); lp_config_set_int(config,key,"reg_sendregister",obj->reg_sendregister); lp_config_set_int(config,key,"publish",obj->publish); + lp_config_set_int(config, key, "avpf", obj->avpf_enabled); + lp_config_set_int(config, key, "avpf_rr_interval", obj->avpf_rr_interval); lp_config_set_int(config,key,"dial_escape_plus",obj->dial_escape_plus); lp_config_set_int(config,key,"send_statistics",obj->send_statistics); lp_config_set_string(config,key,"dial_prefix",obj->dial_prefix); @@ -1229,6 +1231,9 @@ LinphoneProxyConfig *linphone_proxy_config_new_from_config_file(LpConfig *config linphone_proxy_config_enable_publish(cfg,lp_config_get_int(config,key,"publish",0)); + linphone_proxy_config_enable_avpf(cfg, lp_config_get_int(config, key, "avpf", 0)); + linphone_proxy_config_set_avpf_rr_interval(cfg, lp_config_get_int(config, key, "avpf_rr_interval", 0)); + linphone_proxy_config_set_dial_escape_plus(cfg,lp_config_get_int(config,key,"dial_escape_plus",lp_config_get_default_int(config,"proxy","dial_escape_plus",0))); linphone_proxy_config_set_dial_prefix(cfg,lp_config_get_string(config,key,"dial_prefix",lp_config_get_default_string(config,"proxy","dial_prefix",NULL))); @@ -1541,3 +1546,16 @@ int linphone_proxy_config_get_publish_expires(const LinphoneProxyConfig *obj) { } } + +void linphone_proxy_config_enable_avpf(LinphoneProxyConfig *cfg, bool_t enable) { + cfg->avpf_enabled = enable; +} + +void linphone_proxy_config_set_avpf_rr_interval(LinphoneProxyConfig *cfg, uint8_t interval) { + if (interval > 5) interval = 5; + cfg->avpf_rr_interval = interval; +} + +uint8_t linphone_proxy_config_get_avpf_rr_interval(const LinphoneProxyConfig *cfg) { + return cfg->avpf_rr_interval; +} diff --git a/include/sal/sal.h b/include/sal/sal.h index d5712c09d..b2ce51e22 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -221,6 +221,7 @@ typedef struct SalMediaDescription{ unsigned int session_ver; unsigned int session_id; SalStreamDir dir; + uint8_t avpf_rr_interval; SalStreamDescription streams[SAL_MEDIA_DESCRIPTION_MAX_STREAMS]; OrtpRtcpXrConfiguration rtcp_xr; char ice_ufrag[SAL_MEDIA_DESCRIPTION_MAX_ICE_UFRAG_LEN]; From 0a40048d4b355cd82dab532ea9c84d14ffb03a73 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 28 May 2014 14:00:52 +0200 Subject: [PATCH 42/94] Add rtcp-fb attributes in the SDP. --- coreapi/bellesip_sal/sal_sdp.c | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/coreapi/bellesip_sal/sal_sdp.c b/coreapi/bellesip_sal/sal_sdp.c index 0cf94bed3..31341fd21 100644 --- a/coreapi/bellesip_sal/sal_sdp.c +++ b/coreapi/bellesip_sal/sal_sdp.c @@ -68,6 +68,40 @@ static void add_ice_remote_candidates(belle_sdp_media_description_t *md, const S if (buffer[0] != '\0') belle_sdp_media_description_add_attribute(md,belle_sdp_attribute_create("remote-candidates",buffer)); } +static void add_rtcp_fb_nack_attribute(belle_sdp_media_description_t *media_desc, int8_t id, belle_sdp_rtcp_fb_val_param_t param) { + belle_sdp_rtcp_fb_attribute_t *attribute = belle_sdp_rtcp_fb_attribute_new(); + belle_sdp_rtcp_fb_attribute_set_id(attribute, id); + belle_sdp_rtcp_fb_attribute_set_type(attribute, BELLE_SDP_RTCP_FB_NACK); + belle_sdp_rtcp_fb_attribute_set_param(attribute, param); + belle_sdp_media_description_add_attribute(media_desc, BELLE_SDP_ATTRIBUTE(attribute)); +} + +static void add_rtcp_fb_attributes(belle_sdp_media_description_t *media_desc, const SalMediaDescription *md, const SalStreamDescription *stream, int nb_avpf_pt) { + MSList *pt_it; + PayloadType *pt; + belle_sdp_rtcp_fb_attribute_t *attribute = belle_sdp_rtcp_fb_attribute_new(); + belle_sdp_rtcp_fb_attribute_set_id(attribute, -1); + belle_sdp_rtcp_fb_attribute_set_type(attribute, BELLE_SDP_RTCP_FB_TRR_INT); + belle_sdp_rtcp_fb_attribute_set_trr_int(attribute, md->avpf_rr_interval); + belle_sdp_media_description_add_attribute(media_desc, BELLE_SDP_ATTRIBUTE(attribute)); + if ((stream->type == SalVideo) && (nb_avpf_pt > 0)) { + if (nb_avpf_pt == ms_list_size(stream->payloads)) { + add_rtcp_fb_nack_attribute(media_desc, -1, BELLE_SDP_RTCP_FB_PLI); + add_rtcp_fb_nack_attribute(media_desc, -1, BELLE_SDP_RTCP_FB_SLI); + add_rtcp_fb_nack_attribute(media_desc, -1, BELLE_SDP_RTCP_FB_RPSI); + } else { + for (pt_it = stream->payloads; pt_it != NULL; pt_it = pt_it->next) { + pt = (PayloadType *) pt_it->data; + if (payload_type_get_flags(pt) & PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED) { + add_rtcp_fb_nack_attribute(media_desc, payload_type_get_number(pt), BELLE_SDP_RTCP_FB_PLI); + add_rtcp_fb_nack_attribute(media_desc, payload_type_get_number(pt), BELLE_SDP_RTCP_FB_SLI); + add_rtcp_fb_nack_attribute(media_desc, payload_type_get_number(pt), BELLE_SDP_RTCP_FB_RPSI); + } + } + } + } +} + static belle_sdp_attribute_t * create_rtcp_xr_attribute(const OrtpRtcpXrConfiguration *config) { belle_sdp_rtcp_xr_attribute_t *attribute = belle_sdp_rtcp_xr_attribute_new(); if (config->rcvr_rtt_mode != OrtpRtcpXrRcvrRttNone) { @@ -100,6 +134,7 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session int rtp_port; int rtcp_port; bool_t different_rtp_and_rtcp_addr; + int nb_avpf_pt = 0; rtp_addr=stream->rtp_addr; rtcp_addr=stream->rtcp_addr; @@ -114,6 +149,7 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session if (stream->payloads) { for ( pt_it=stream->payloads; pt_it!=NULL; pt_it=pt_it->next ) { pt= ( PayloadType* ) pt_it->data; + if (payload_type_get_flags(pt) & PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED) nb_avpf_pt++; mime_param= belle_sdp_mime_parameter_create ( pt->mime_type , payload_type_get_number ( pt ) , pt->clock_rate @@ -201,6 +237,10 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session } } + if ((stream->proto == SalProtoRtpAvpf) || (stream->proto == SalProtoRtpSavpf)) { + add_rtcp_fb_attributes(media_desc, md, stream, nb_avpf_pt); + } + if (stream->rtcp_xr.enabled == TRUE) { char sastr[1024] = {0}; char mastr[1024] = {0}; From cc4095de90237d527de2af48bca910b3df6ec4f9 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 28 May 2014 14:01:07 +0200 Subject: [PATCH 43/94] Do not unset the PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED flag anymore. --- coreapi/linphonecall.c | 1 - 1 file changed, 1 deletion(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 84168419c..63ea19a16 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -197,7 +197,6 @@ static MSList *make_codec_list(LinphoneCore *lc, const MSList *codecs, int bandw if (max_sample_rate) *max_sample_rate=0; for(it=codecs;it!=NULL;it=it->next){ PayloadType *pt=(PayloadType*)it->data; - payload_type_unset_flag(pt, PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED); /* Disable AVPF for the moment. */ if (pt->flags & PAYLOAD_TYPE_ENABLED){ if (bandwidth_limit>0 && !linphone_core_is_payload_type_usable_for_bandwidth(lc,pt,bandwidth_limit)){ ms_message("Codec %s/%i eliminated because of audio bandwidth constraint of %i kbit/s", From ad64b94401f02d2d9fc308430391c05ce9948d9d Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 30 May 2014 14:25:51 +0200 Subject: [PATCH 44/94] Parse rtcp-fb attributes contained in SDP. --- coreapi/bellesip_sal/sal_sdp.c | 136 ++++++++++++++++++++++++++++----- coreapi/linphonecore.c | 5 +- coreapi/linphonecore.h | 7 ++ coreapi/offeranswer.c | 8 +- coreapi/proxy.c | 4 + 5 files changed, 134 insertions(+), 26 deletions(-) diff --git a/coreapi/bellesip_sal/sal_sdp.c b/coreapi/bellesip_sal/sal_sdp.c index 31341fd21..628fc6974 100644 --- a/coreapi/bellesip_sal/sal_sdp.c +++ b/coreapi/bellesip_sal/sal_sdp.c @@ -76,28 +76,34 @@ static void add_rtcp_fb_nack_attribute(belle_sdp_media_description_t *media_desc belle_sdp_media_description_add_attribute(media_desc, BELLE_SDP_ATTRIBUTE(attribute)); } -static void add_rtcp_fb_attributes(belle_sdp_media_description_t *media_desc, const SalMediaDescription *md, const SalStreamDescription *stream, int nb_avpf_pt) { +static void add_rtcp_fb_attributes(belle_sdp_media_description_t *media_desc, const SalMediaDescription *md, const SalStreamDescription *stream) { MSList *pt_it; PayloadType *pt; + PayloadTypeAvpfParams avpf_params; belle_sdp_rtcp_fb_attribute_t *attribute = belle_sdp_rtcp_fb_attribute_new(); + belle_sdp_rtcp_fb_attribute_set_id(attribute, -1); belle_sdp_rtcp_fb_attribute_set_type(attribute, BELLE_SDP_RTCP_FB_TRR_INT); belle_sdp_rtcp_fb_attribute_set_trr_int(attribute, md->avpf_rr_interval); belle_sdp_media_description_add_attribute(media_desc, BELLE_SDP_ATTRIBUTE(attribute)); - if ((stream->type == SalVideo) && (nb_avpf_pt > 0)) { - if (nb_avpf_pt == ms_list_size(stream->payloads)) { - add_rtcp_fb_nack_attribute(media_desc, -1, BELLE_SDP_RTCP_FB_PLI); - add_rtcp_fb_nack_attribute(media_desc, -1, BELLE_SDP_RTCP_FB_SLI); - add_rtcp_fb_nack_attribute(media_desc, -1, BELLE_SDP_RTCP_FB_RPSI); - } else { - for (pt_it = stream->payloads; pt_it != NULL; pt_it = pt_it->next) { - pt = (PayloadType *) pt_it->data; - if (payload_type_get_flags(pt) & PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED) { - add_rtcp_fb_nack_attribute(media_desc, payload_type_get_number(pt), BELLE_SDP_RTCP_FB_PLI); - add_rtcp_fb_nack_attribute(media_desc, payload_type_get_number(pt), BELLE_SDP_RTCP_FB_SLI); - add_rtcp_fb_nack_attribute(media_desc, payload_type_get_number(pt), BELLE_SDP_RTCP_FB_RPSI); - } - } + + for (pt_it = stream->payloads; pt_it != NULL; pt_it = pt_it->next) { + pt = (PayloadType *)pt_it->data; + + /* AVPF/SAVPF profile is used so enable AVPF for all paylad types. */ + payload_type_set_flag(pt, PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED); + avpf_params = payload_type_get_avpf_params(pt); + avpf_params.trr_interval = md->avpf_rr_interval; + + /* Add rtcp-fb attributes according to the AVPF features of the payload types. */ + if (avpf_params.features & PAYLOAD_TYPE_AVPF_PLI) { + add_rtcp_fb_nack_attribute(media_desc, payload_type_get_number(pt), BELLE_SDP_RTCP_FB_PLI); + } + if (avpf_params.features & PAYLOAD_TYPE_AVPF_SLI) { + add_rtcp_fb_nack_attribute(media_desc, payload_type_get_number(pt), BELLE_SDP_RTCP_FB_SLI); + } + if (avpf_params.features & PAYLOAD_TYPE_AVPF_RPSI) { + add_rtcp_fb_nack_attribute(media_desc, payload_type_get_number(pt), BELLE_SDP_RTCP_FB_RPSI); } } } @@ -134,13 +140,12 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session int rtp_port; int rtcp_port; bool_t different_rtp_and_rtcp_addr; - int nb_avpf_pt = 0; - + rtp_addr=stream->rtp_addr; rtcp_addr=stream->rtcp_addr; rtp_port=stream->rtp_port; rtcp_port=stream->rtcp_port; - + media_desc = belle_sdp_media_description_create ( sal_stream_description_get_type_as_string(stream) ,stream->rtp_port ,1 @@ -149,7 +154,6 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session if (stream->payloads) { for ( pt_it=stream->payloads; pt_it!=NULL; pt_it=pt_it->next ) { pt= ( PayloadType* ) pt_it->data; - if (payload_type_get_flags(pt) & PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED) nb_avpf_pt++; mime_param= belle_sdp_mime_parameter_create ( pt->mime_type , payload_type_get_number ( pt ) , pt->clock_rate @@ -175,7 +179,7 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session }else inet6=FALSE; belle_sdp_media_description_set_connection(media_desc,belle_sdp_connection_create("IN", inet6 ? "IP6" : "IP4", rtp_addr)); } - + if ( stream->bandwidth>0 ) belle_sdp_media_description_set_bandwidth ( media_desc,"AS",stream->bandwidth ); @@ -238,7 +242,7 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session } if ((stream->proto == SalProtoRtpAvpf) || (stream->proto == SalProtoRtpSavpf)) { - add_rtcp_fb_attributes(media_desc, md, stream, nb_avpf_pt); + add_rtcp_fb_attributes(media_desc, md, stream); } if (stream->rtcp_xr.enabled == TRUE) { @@ -324,9 +328,11 @@ belle_sdp_session_description_t * media_description_to_sdp ( const SalMediaDescr static void sdp_parse_payload_types(belle_sdp_media_description_t *media_desc, SalStreamDescription *stream) { PayloadType *pt; + PayloadTypeAvpfParams avpf_params; belle_sip_list_t* mime_param_it=NULL; belle_sdp_mime_parameter_t* mime_param; belle_sip_list_t* mime_params=belle_sdp_media_description_build_mime_parameters ( media_desc ); + memset(&avpf_params, 0, sizeof(avpf_params)); for ( mime_param_it=mime_params ; mime_param_it!=NULL ; mime_param_it=mime_param_it->next ) { @@ -338,6 +344,7 @@ static void sdp_parse_payload_types(belle_sdp_media_description_t *media_desc, S pt->mime_type=ms_strdup ( belle_sdp_mime_parameter_get_type ( mime_param ) ); pt->channels=belle_sdp_mime_parameter_get_channel_count ( mime_param ); payload_type_set_send_fmtp ( pt,belle_sdp_mime_parameter_get_parameters ( mime_param ) ); + payload_type_set_avpf_params(pt, avpf_params); stream->payloads=ms_list_append ( stream->payloads,pt ); stream->ptime=belle_sdp_mime_parameter_get_ptime ( mime_param ); ms_message ( "Found payload %s/%i fmtp=%s",pt->mime_type,pt->clock_rate, @@ -441,6 +448,87 @@ static void sdp_parse_media_ice_parameters(belle_sdp_media_description_t *media_ } } +static void enable_avpf_for_stream(SalStreamDescription *stream) { + MSList *pt_it; + PayloadType *pt; + PayloadTypeAvpfParams avpf_params; + + for (pt_it = stream->payloads; pt_it != NULL; pt_it = pt_it->next) { + pt = (PayloadType *)pt_it->data; + avpf_params = payload_type_get_avpf_params(pt); + payload_type_set_flag(pt, PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED); + avpf_params.features |= PAYLOAD_TYPE_AVPF_FIR; + avpf_params.trr_interval = 0; + payload_type_set_avpf_params(pt, avpf_params); + } +} + +static void apply_rtcp_fb_attribute_to_payload(belle_sdp_rtcp_fb_attribute_t *fb_attribute, PayloadType *pt) { + PayloadTypeAvpfParams avpf_params = payload_type_get_avpf_params(pt); + switch (belle_sdp_rtcp_fb_attribute_get_type(fb_attribute)) { + case BELLE_SDP_RTCP_FB_NACK: + switch (belle_sdp_rtcp_fb_attribute_get_param(fb_attribute)) { + case BELLE_SDP_RTCP_FB_PLI: + avpf_params.features |= PAYLOAD_TYPE_AVPF_PLI; + break; + case BELLE_SDP_RTCP_FB_SLI: + avpf_params.features |= PAYLOAD_TYPE_AVPF_SLI; + break; + case BELLE_SDP_RTCP_FB_RPSI: + avpf_params.features |= PAYLOAD_TYPE_AVPF_RPSI; + break; + default: + break; + } + break; + case BELLE_SDP_RTCP_FB_TRR_INT: + avpf_params.trr_interval = (unsigned char)belle_sdp_rtcp_fb_attribute_get_trr_int(fb_attribute); + break; + case BELLE_SDP_RTCP_FB_ACK: + default: + break; + } + payload_type_set_avpf_params(pt, avpf_params); +} + +static void sdp_parse_rtcp_fb_parameters(belle_sdp_media_description_t *media_desc, SalStreamDescription *stream) { + belle_sip_list_t *it; + belle_sdp_attribute_t *attribute; + belle_sdp_rtcp_fb_attribute_t *fb_attribute; + MSList *pt_it; + PayloadType *pt; + int8_t pt_num; + + /* Handle rtcp-fb attributes that concern all payload types. */ + for (it = belle_sdp_media_description_get_attributes(media_desc); it != NULL; it = it->next) { + attribute = BELLE_SDP_ATTRIBUTE(it->data); + if (keywordcmp("rtcp-fb", belle_sdp_attribute_get_name(attribute)) == 0) { + fb_attribute = BELLE_SDP_RTCP_FB_ATTRIBUTE(attribute); + if (belle_sdp_rtcp_fb_attribute_get_id(fb_attribute) == -1) { + for (pt_it = stream->payloads; pt_it != NULL; pt_it = pt_it->next) { + pt = (PayloadType *)pt_it->data; + apply_rtcp_fb_attribute_to_payload(fb_attribute, pt); + } + } + } + } + + /* Handle rtcp-fb attributes that are specefic to a payload type. */ + for (it = belle_sdp_media_description_get_attributes(media_desc); it != NULL; it = it->next) { + attribute = BELLE_SDP_ATTRIBUTE(it->data); + if (keywordcmp("rtcp-fb", belle_sdp_attribute_get_name(attribute)) == 0) { + fb_attribute = BELLE_SDP_RTCP_FB_ATTRIBUTE(attribute); + pt_num = belle_sdp_rtcp_fb_attribute_get_id(fb_attribute); + for (pt_it = stream->payloads; pt_it != NULL; pt_it = pt_it->next) { + pt = (PayloadType *)pt_it->data; + if (payload_type_get_number(pt) == (int)pt_num) { + apply_rtcp_fb_attribute_to_payload(fb_attribute, pt); + } + } + } + } +} + static void sal_init_rtcp_xr_description(OrtpRtcpXrConfiguration *config) { config->enabled = FALSE; config->rcvr_rtt_mode = OrtpRtcpXrRcvrRttNone; @@ -583,6 +671,12 @@ static SalStreamDescription * sdp_to_stream_description(SalMediaDescription *md, /* Get ICE candidate attributes if any */ sdp_parse_media_ice_parameters(media_desc, stream); + /* Get RTCP-FB attributes if any */ + if ((stream->proto == SalProtoRtpAvpf) || (stream->proto == SalProtoRtpSavpf)) { + enable_avpf_for_stream(stream); + sdp_parse_rtcp_fb_parameters(media_desc, stream); + } + /* Get RTCP-XR attributes if any */ stream->rtcp_xr = md->rtcp_xr; // Use session parameters if no stream parameters are defined sdp_parse_media_rtcp_xr_parameters(media_desc, &stream->rtcp_xr); diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 65b18e6e8..d593a1dbd 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -2840,8 +2840,8 @@ LinphoneCall * linphone_core_invite_address_with_params(LinphoneCore *lc, const if (proxy!=NULL) { from=linphone_proxy_config_get_identity(proxy); - cp->avpf_enabled = proxy->avpf_enabled; - cp->avpf_rr_interval = proxy->avpf_rr_interval; + cp->avpf_enabled = linphone_proxy_config_is_avpf_enabled(proxy); + cp->avpf_rr_interval = linphone_proxy_config_get_avpf_rr_interval(proxy); } /* if no proxy or no identity defined for this proxy, default to primary contact*/ @@ -6438,6 +6438,7 @@ void linphone_core_init_default_params(LinphoneCore*lc, LinphoneCallParams *para params->media_encryption=linphone_core_get_media_encryption(lc); params->in_conference=FALSE; params->privacy=LinphonePrivacyDefault; + params->avpf_enabled=TRUE; } void linphone_core_set_device_identifier(LinphoneCore *lc,const char* device_id) { diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index b9a199519..b21ebc7ca 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -936,6 +936,13 @@ LINPHONE_PUBLIC LinphonePrivacyMask linphone_proxy_config_get_privacy(const Linp */ LINPHONE_PUBLIC void linphone_proxy_config_enable_avpf(LinphoneProxyConfig *cfg, bool_t enable); +/** + * Indicates whether AVPF/SAVPF is being used for calls using this proxy config. + * @param[in] cfg #LinphoneProxyConfig object + * @return True if AVPF/SAVPF is enabled, false otherwise. + */ +LINPHONE_PUBLIC bool_t linphone_proxy_config_is_avpf_enabled(LinphoneProxyConfig *cfg); + /** * Set the interval between regular RTCP reports when using AVPF/SAVPF. * @param[in] cfg #LinphoneProxyConfig object diff --git a/coreapi/offeranswer.c b/coreapi/offeranswer.c index be8998168..7b38aaed7 100644 --- a/coreapi/offeranswer.c +++ b/coreapi/offeranswer.c @@ -324,9 +324,11 @@ static bool_t local_stream_not_already_used(const SalMediaDescription *result, c } /*in answering mode, we consider that if we are able to make AVPF/SAVP/SAVPF, then we can do AVP as well*/ -static bool_t proto_compatible(SalMediaProto local, SalMediaProto remote){ - if (local==remote) return TRUE; - if ((remote==SalProtoRtpAvp) && ((local==SalProtoRtpSavp) || (local==SalProtoRtpAvpf) || (local==SalProtoRtpSavpf))) +static bool_t proto_compatible(SalMediaProto local, SalMediaProto remote) { + if (local == remote) return TRUE; + if ((remote == SalProtoRtpAvpf) && (local == SalProtoRtpSavpf)) + return TRUE; + if ((remote == SalProtoRtpAvp) && ((local == SalProtoRtpSavp) || (local == SalProtoRtpAvpf) || (local == SalProtoRtpSavpf))) return TRUE; return FALSE; } diff --git a/coreapi/proxy.c b/coreapi/proxy.c index 278fd9be6..c85cc33dd 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -1551,6 +1551,10 @@ void linphone_proxy_config_enable_avpf(LinphoneProxyConfig *cfg, bool_t enable) cfg->avpf_enabled = enable; } +bool_t linphone_proxy_config_is_avpf_enabled(LinphoneProxyConfig *cfg) { + return cfg->avpf_enabled; +} + void linphone_proxy_config_set_avpf_rr_interval(LinphoneProxyConfig *cfg, uint8_t interval) { if (interval > 5) interval = 5; cfg->avpf_rr_interval = interval; From c82463afc1320a9b9046166afd97a2e6a46c0680 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 30 May 2014 14:50:19 +0200 Subject: [PATCH 45/94] Handle offer/answer of rtcp-fb parameters. --- coreapi/offeranswer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/coreapi/offeranswer.c b/coreapi/offeranswer.c index 7b38aaed7..2d89edcf7 100644 --- a/coreapi/offeranswer.c +++ b/coreapi/offeranswer.c @@ -99,6 +99,10 @@ static MSList *match_payloads(const MSList *local, const MSList *remote, bool_t if (p2->send_fmtp) payload_type_set_send_fmtp(newp,p2->send_fmtp); newp->flags|=PAYLOAD_TYPE_FLAG_CAN_RECV|PAYLOAD_TYPE_FLAG_CAN_SEND; + if (p2->flags & PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED) { + newp->flags |= PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED; + newp->avpf = payload_type_get_avpf_params(p2); + } res=ms_list_append(res,newp); /* we should use the remote numbering even when parsing a response */ payload_type_set_number(newp,remote_number); From 0cd71d6548000a88253b85097d19529ff914d604 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 30 May 2014 16:43:15 +0200 Subject: [PATCH 46/94] Fix issue with rtcp-fb attributes in SDP of response. --- coreapi/bellesip_sal/sal_sdp.c | 41 ++++++++++++++++++++++++++++------ coreapi/linphonecall.c | 27 +++++++++++++++++++++- include/sal/sal.h | 1 - 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/coreapi/bellesip_sal/sal_sdp.c b/coreapi/bellesip_sal/sal_sdp.c index 628fc6974..158460377 100644 --- a/coreapi/bellesip_sal/sal_sdp.c +++ b/coreapi/bellesip_sal/sal_sdp.c @@ -68,6 +68,31 @@ static void add_ice_remote_candidates(belle_sdp_media_description_t *md, const S if (buffer[0] != '\0') belle_sdp_media_description_add_attribute(md,belle_sdp_attribute_create("remote-candidates",buffer)); } +static bool_t is_rtcp_fb_trr_int_the_same_for_all_payloads(const SalStreamDescription *stream, uint8_t *trr_int) { + MSList *pt_it; + bool_t first = TRUE; + for (pt_it = stream->payloads; pt_it != NULL; pt_it = pt_it->next) { + PayloadType *pt = (PayloadType *)pt_it->data; + if (payload_type_get_flags(pt) & PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED) { + if (first == TRUE) { + *trr_int = payload_type_get_avpf_params(pt).trr_interval; + first = FALSE; + } else if (payload_type_get_avpf_params(pt).trr_interval != *trr_int) { + return FALSE; + } + } + } + return TRUE; +} + +static void add_rtcp_fb_trr_int_attribute(belle_sdp_media_description_t *media_desc, int8_t id, uint8_t trr_int) { + belle_sdp_rtcp_fb_attribute_t *attribute = belle_sdp_rtcp_fb_attribute_new(); + belle_sdp_rtcp_fb_attribute_set_id(attribute, id); + belle_sdp_rtcp_fb_attribute_set_type(attribute, BELLE_SDP_RTCP_FB_TRR_INT); + belle_sdp_rtcp_fb_attribute_set_trr_int(attribute, trr_int); + belle_sdp_media_description_add_attribute(media_desc, BELLE_SDP_ATTRIBUTE(attribute)); +} + static void add_rtcp_fb_nack_attribute(belle_sdp_media_description_t *media_desc, int8_t id, belle_sdp_rtcp_fb_val_param_t param) { belle_sdp_rtcp_fb_attribute_t *attribute = belle_sdp_rtcp_fb_attribute_new(); belle_sdp_rtcp_fb_attribute_set_id(attribute, id); @@ -80,12 +105,13 @@ static void add_rtcp_fb_attributes(belle_sdp_media_description_t *media_desc, co MSList *pt_it; PayloadType *pt; PayloadTypeAvpfParams avpf_params; - belle_sdp_rtcp_fb_attribute_t *attribute = belle_sdp_rtcp_fb_attribute_new(); + bool_t general_trr_int; + uint8_t trr_int = 0; - belle_sdp_rtcp_fb_attribute_set_id(attribute, -1); - belle_sdp_rtcp_fb_attribute_set_type(attribute, BELLE_SDP_RTCP_FB_TRR_INT); - belle_sdp_rtcp_fb_attribute_set_trr_int(attribute, md->avpf_rr_interval); - belle_sdp_media_description_add_attribute(media_desc, BELLE_SDP_ATTRIBUTE(attribute)); + general_trr_int = is_rtcp_fb_trr_int_the_same_for_all_payloads(stream, &trr_int); + if (general_trr_int == TRUE) { + add_rtcp_fb_trr_int_attribute(media_desc, -1, trr_int); + } for (pt_it = stream->payloads; pt_it != NULL; pt_it = pt_it->next) { pt = (PayloadType *)pt_it->data; @@ -93,7 +119,6 @@ static void add_rtcp_fb_attributes(belle_sdp_media_description_t *media_desc, co /* AVPF/SAVPF profile is used so enable AVPF for all paylad types. */ payload_type_set_flag(pt, PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED); avpf_params = payload_type_get_avpf_params(pt); - avpf_params.trr_interval = md->avpf_rr_interval; /* Add rtcp-fb attributes according to the AVPF features of the payload types. */ if (avpf_params.features & PAYLOAD_TYPE_AVPF_PLI) { @@ -457,7 +482,9 @@ static void enable_avpf_for_stream(SalStreamDescription *stream) { pt = (PayloadType *)pt_it->data; avpf_params = payload_type_get_avpf_params(pt); payload_type_set_flag(pt, PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED); - avpf_params.features |= PAYLOAD_TYPE_AVPF_FIR; + if (stream->type == SalVideo) { + avpf_params.features |= PAYLOAD_TYPE_AVPF_FIR; + } avpf_params.trr_interval = 0; payload_type_set_avpf_params(pt, avpf_params); } diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 63ea19a16..25be65fe4 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -281,6 +281,31 @@ static void setup_encryption_keys(LinphoneCall *call, SalMediaDescription *md){ } } +static void setup_rtcp_fb(LinphoneCall *call, SalMediaDescription *md) { + MSList *pt_it; + PayloadType *pt; + PayloadTypeAvpfParams avpf_params; + int i; + + for (i = 0; i < md->n_active_streams; i++) { + for (pt_it = md->streams[i].payloads; pt_it != NULL; pt_it = pt_it->next) { + pt = (PayloadType *)pt_it->data; + if (call->params.avpf_enabled == TRUE) { + payload_type_set_flag(pt, PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED); + avpf_params = payload_type_get_avpf_params(pt); + avpf_params.trr_interval = call->params.avpf_rr_interval; + if (md->streams[i].type == SalVideo) { + avpf_params.features |= PAYLOAD_TYPE_AVPF_FIR; + } + } else { + payload_type_unset_flag(pt, PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED); + memset(&avpf_params, 0, sizeof(avpf_params)); + } + payload_type_set_avpf_params(pt, avpf_params); + } + } +} + static void setup_rtcp_xr(LinphoneCall *call, SalMediaDescription *md) { LinphoneCore *lc = call->core; int i; @@ -339,7 +364,6 @@ void linphone_call_make_local_media_description(LinphoneCore *lc, LinphoneCall * md->session_id=(old_md ? old_md->session_id : (rand() & 0xfff)); md->session_ver=(old_md ? (old_md->session_ver+1) : (rand() & 0xfff)); md->n_total_streams=(call->biggestdesc ? call->biggestdesc->n_total_streams : 1); - md->avpf_rr_interval = call->params.avpf_rr_interval; strncpy(md->addr,local_ip,sizeof(md->addr)); strncpy(md->username,linphone_address_get_username(addr),sizeof(md->username)); @@ -392,6 +416,7 @@ void linphone_call_make_local_media_description(LinphoneCore *lc, LinphoneCall * } setup_encryption_keys(call,md); + setup_rtcp_fb(call, md); setup_rtcp_xr(call, md); update_media_description_from_stun(md,&call->ac,&call->vc); diff --git a/include/sal/sal.h b/include/sal/sal.h index b2ce51e22..d5712c09d 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -221,7 +221,6 @@ typedef struct SalMediaDescription{ unsigned int session_ver; unsigned int session_id; SalStreamDir dir; - uint8_t avpf_rr_interval; SalStreamDescription streams[SAL_MEDIA_DESCRIPTION_MAX_STREAMS]; OrtpRtcpXrConfiguration rtcp_xr; char ice_ufrag[SAL_MEDIA_DESCRIPTION_MAX_ICE_UFRAG_LEN]; From d025d8bce0473ce8c52cc6edbe5b3f2b044d29c0 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 2 Jun 2014 10:46:21 +0200 Subject: [PATCH 47/94] Remove useless traces. --- coreapi/linphonecall.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 25be65fe4..2bf68fc99 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -1462,7 +1462,6 @@ static void rendercb(void *data, const MSPicture *local, const MSPicture *remote #ifdef VIDEO_ENABLED static void video_stream_event_cb(void *user_pointer, const MSFilter *f, const unsigned int event_id, const void *args){ LinphoneCall* call = (LinphoneCall*) user_pointer; - ms_warning("In linphonecall.c: video_stream_event_cb"); switch (event_id) { case MS_VIDEO_DECODER_DECODING_ERRORS: ms_warning("Case is MS_VIDEO_DECODER_DECODING_ERRORS"); @@ -1476,6 +1475,11 @@ static void video_stream_event_cb(void *user_pointer, const MSFilter *f, const u if (call->nextVideoFrameDecoded._func != NULL) call->nextVideoFrameDecoded._func(call, call->nextVideoFrameDecoded._user_data); break; + case MS_VIDEO_DECODER_SEND_PLI: + case MS_VIDEO_DECODER_SEND_SLI: + case MS_VIDEO_DECODER_SEND_RPSI: + /* Handled internally by mediastreamer2. */ + break; default: ms_warning("Unhandled event %i", event_id); break; From 450489e25e1d1c59b4202b68aee77890011604c9 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 2 Jun 2014 11:04:08 +0200 Subject: [PATCH 48/94] Update oRTP and ms2 submodules for changes on AVPF. --- mediastreamer2 | 2 +- oRTP | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mediastreamer2 b/mediastreamer2 index 5db89bd64..60b83538c 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 5db89bd644eb5a692c4b570e06a199943ff6cb26 +Subproject commit 60b83538cd75a57703fd5267d40adc1b556f3245 diff --git a/oRTP b/oRTP index a6c420eed..8c0d0ec22 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit a6c420eedd03c677f5560c3b37bf2b5684c6b0f9 +Subproject commit 8c0d0ec22ad4cac478584da6663be1409b1f9ec1 From a29a93cd765e405472e2344a337ae57a08375aac Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Mon, 2 Jun 2014 14:05:30 +0200 Subject: [PATCH 49/94] Add an API to set and retrieve chat messages "app data". This allows clients to store resilient data for each messages --- coreapi/chat.c | 32 +++++++++++++++++++++++ coreapi/linphonecore.h | 8 +++--- coreapi/message_storage.c | 53 +++++++++++++++++++++++++++++++++------ coreapi/private.h | 2 ++ 4 files changed, 85 insertions(+), 10 deletions(-) diff --git a/coreapi/chat.c b/coreapi/chat.c index fb2a34159..2ea196b47 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -668,6 +668,36 @@ void linphone_chat_message_set_external_body_url(LinphoneChatMessage* message,co message->external_body_url=url?ms_strdup(url):NULL; } + +/** + * Linphone message has an app-specific field that can store a text. The application might want + * to use it for keeping data over restarts, like thumbnail image path. + * @param message #LinphoneChatMessage + * @return the application-specific data or NULL if none has been stored. + */ +const char* linphone_chat_message_get_appdata(const LinphoneChatMessage* message){ + return message->appdata; +} + +/** + * Linphone message has an app-specific field that can store a text. The application might want + * to use it for keeping data over restarts, like thumbnail image path. + * + * Invoking this function will attempt to update the message storage to reflect the changeif it is + * enabled. + * + * @param message #LinphoneChatMessage + * @param data the data to store into the message + */ +void linphone_chat_message_set_appdata(LinphoneChatMessage* message, const char* data){ + if( message->appdata ){ + ms_free(message->appdata); + } + message->appdata = data? ms_strdup(data) : NULL; + linphone_chat_message_store_appdata(message); +} + + /** * Set origin of the message *@param message #LinphoneChatMessage obj @@ -805,6 +835,7 @@ LinphoneChatMessage* linphone_chat_message_clone(const LinphoneChatMessage* msg) };*/ LinphoneChatMessage* new_message = linphone_chat_room_create_message(msg->chat_room,msg->message); if (msg->external_body_url) new_message->external_body_url=ms_strdup(msg->external_body_url); + if (msg->appdata) new_message->appdata = ms_strdup(msg->appdata); new_message->cb=msg->cb; new_message->cb_ud=msg->cb_ud; new_message->message_userdata=msg->message_userdata; @@ -831,6 +862,7 @@ static void _linphone_chat_message_destroy(LinphoneChatMessage* msg) { if (msg->op) sal_op_release(msg->op); if (msg->message) ms_free(msg->message); if (msg->external_body_url) ms_free(msg->external_body_url); + if (msg->appdata) ms_free(msg->appdata); if (msg->from) linphone_address_destroy(msg->from); if (msg->to) linphone_address_destroy(msg->to); if (msg->custom_headers) sal_custom_header_free(msg->custom_headers); diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index b21ebc7ca..c2d565695 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -1130,7 +1130,9 @@ LINPHONE_PUBLIC void linphone_chat_message_set_to(LinphoneChatMessage* message, LINPHONE_PUBLIC const LinphoneAddress* linphone_chat_message_get_to(const LinphoneChatMessage* message); LINPHONE_PUBLIC const char* linphone_chat_message_get_external_body_url(const LinphoneChatMessage* message); LINPHONE_PUBLIC void linphone_chat_message_set_external_body_url(LinphoneChatMessage* message,const char* url); -LINPHONE_PUBLIC const char * linphone_chat_message_get_text(const LinphoneChatMessage* message); +LINPHONE_PUBLIC const char* linphone_chat_message_get_appdata(const LinphoneChatMessage* message); +LINPHONE_PUBLIC void linphone_chat_message_set_appdata(LinphoneChatMessage* message, const char* data); +LINPHONE_PUBLIC const char* linphone_chat_message_get_text(const LinphoneChatMessage* message); LINPHONE_PUBLIC time_t linphone_chat_message_get_time(const LinphoneChatMessage* message); LINPHONE_PUBLIC void* linphone_chat_message_get_user_data(const LinphoneChatMessage* message); LINPHONE_PUBLIC void linphone_chat_message_set_user_data(LinphoneChatMessage* message,void*); @@ -1622,7 +1624,7 @@ LINPHONE_PUBLIC bool_t linphone_core_payload_type_is_vbr(LinphoneCore *lc, const * @param[in] lc the #LinphoneCore object * @param[in] pt the #PayloadType to modify. * @param[in] bitrate the IP bitrate in kbit/s. - * @ingroup media_parameters + * @ingroup media_parameters **/ LINPHONE_PUBLIC void linphone_core_set_payload_type_bitrate(LinphoneCore *lc, PayloadType *pt, int bitrate); @@ -1631,7 +1633,7 @@ LINPHONE_PUBLIC void linphone_core_set_payload_type_bitrate(LinphoneCore *lc, Pa * @param[in] lc the #LinphoneCore object * @param[in] pt the #PayloadType to modify. * @return bitrate the IP bitrate in kbit/s, or -1 if an error occured. - * @ingroup media_parameters + * @ingroup media_parameters **/ LINPHONE_PUBLIC int linphone_core_get_payload_type_bitrate(LinphoneCore *lc, const PayloadType *pt); diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index 81d076fc5..7caf4536b 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -37,6 +37,20 @@ static inline LinphoneChatMessage* get_transient_message(LinphoneChatRoom* cr, u return NULL; } + +/* DB layout: + * | 0 | storage_id + * | 1 | localContact + * | 2 | remoteContact + * | 3 | direction flag + * | 4 | message + * | 5 | time (unused now, used to be string-based timestamp) + * | 6 | read flag + * | 7 | status + * | 8 | external body url + * | 9 | utc timestamp + * | 10 | app data text + */ static void create_chat_message(char **argv, void *data){ LinphoneChatRoom *cr = (LinphoneChatRoom *)data; LinphoneAddress *from; @@ -67,7 +81,8 @@ static void create_chat_message(char **argv, void *data){ new_message->is_read=atoi(argv[6]); new_message->state=atoi(argv[7]); new_message->storage_id=storage_id; - new_message->external_body_url=argv[8]?ms_strdup(argv[8]):NULL; + new_message->external_body_url= argv[8] ? ms_strdup(argv[8]) : NULL; + new_message->appdata = argv[10]? ms_strdup(argv[10]) : NULL; } cr->messages_hist=ms_list_prepend(cr->messages_hist,new_message); } @@ -95,7 +110,7 @@ void linphone_sql_request_message(sqlite3 *db,const char *stmt,LinphoneChatRoom } } -void linphone_sql_request(sqlite3* db,const char *stmt){ +int linphone_sql_request(sqlite3* db,const char *stmt){ char* errmsg=NULL; int ret; ret=sqlite3_exec(db,stmt,NULL,NULL,&errmsg); @@ -103,6 +118,7 @@ void linphone_sql_request(sqlite3* db,const char *stmt){ ms_error("linphone_sql_request: error sqlite3_exec(): %s.\n", errmsg); sqlite3_free(errmsg); } + return ret; } // Process the request to fetch all chat contacts @@ -123,7 +139,7 @@ unsigned int linphone_chat_message_store(LinphoneChatMessage *msg){ if (lc->db){ char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(msg->chat_room)); char *local_contact=linphone_address_as_string_uri_only(linphone_chat_message_get_local_address(msg)); - char *buf=sqlite3_mprintf("INSERT INTO history VALUES(NULL,%Q,%Q,%i,%Q,%Q,%i,%i,%Q,%i);", + char *buf=sqlite3_mprintf("INSERT INTO history VALUES(NULL,%Q,%Q,%i,%Q,%Q,%i,%i,%Q,%i,%Q);", local_contact, peer, msg->dir, @@ -132,7 +148,8 @@ unsigned int linphone_chat_message_store(LinphoneChatMessage *msg){ msg->is_read, msg->state, msg->external_body_url, - msg->time); + msg->time, + msg->appdata); linphone_sql_request(lc->db,buf); sqlite3_free(buf); ms_free(local_contact); @@ -159,6 +176,16 @@ void linphone_chat_message_store_state(LinphoneChatMessage *msg){ } } +void linphone_chat_message_store_appdata(LinphoneChatMessage* msg){ + LinphoneCore *lc=msg->chat_room->lc; + if (lc->db){ + char *buf=sqlite3_mprintf("UPDATE history SET appdata=%Q WHERE id=%i;", + msg->appdata,msg->storage_id); + linphone_sql_request(lc->db,buf); + sqlite3_free(buf); + } +} + void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){ LinphoneCore *lc=linphone_chat_room_get_lc(cr); int read=1; @@ -302,11 +329,11 @@ static time_t parse_time_from_db( const char* time ){ } -static int migrate_messages(void* data,int argc, char** argv, char** column_names) { +static int migrate_messages_timestamp(void* data,int argc, char** argv, char** column_names) { time_t new_time = parse_time_from_db(argv[1]); if( new_time ){ /* replace 'time' by -1 and set 'utc' to the timestamp */ - char *buf = sqlite3_mprintf("UPDATE history SET utc=%i,time='-1' WHERE id=%i", new_time, atoi(argv[0])); + char *buf = sqlite3_mprintf("UPDATE history SET utc=%i,time='-1' WHERE id=%i;", new_time, atoi(argv[0])); if( buf) { linphone_sql_request((sqlite3*)data, buf); sqlite3_free(buf); @@ -324,7 +351,7 @@ static void linphone_migrate_timestamps(sqlite3* db){ linphone_sql_request(db,"BEGIN TRANSACTION"); - ret = sqlite3_exec(db,"SELECT id,time,direction FROM history WHERE time != '-1'", migrate_messages, db, &errmsg); + ret = sqlite3_exec(db,"SELECT id,time,direction FROM history WHERE time != '-1';", migrate_messages_timestamp, db, &errmsg); if( ret != SQLITE_OK ){ ms_warning("Error migrating outgoing messages: %s.\n", errmsg); sqlite3_free(errmsg); @@ -359,6 +386,15 @@ void linphone_update_table(sqlite3* db) { // migrate from old text-based timestamps to unix time-based timestamps linphone_migrate_timestamps(db); } + + // new field for app-specific storage + ret=sqlite3_exec(db,"ALTER TABLE history ADD COLUMN appdata TEXT;",NULL,NULL,&errmsg); + if(ret != SQLITE_OK) { + ms_message("Table already up to date: %s.", errmsg); + sqlite3_free(errmsg); + } else { + ms_debug("Table updated successfully for app-specific data."); + } } void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) { @@ -431,6 +467,9 @@ unsigned int linphone_chat_message_store(LinphoneChatMessage *cr){ void linphone_chat_message_store_state(LinphoneChatMessage *cr){ } +void linphone_chat_message_store_appdata(LinphoneChatMessage *msg){ +} + void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){ } diff --git a/coreapi/private.h b/coreapi/private.h index 589297849..ac93f41b9 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -144,6 +144,7 @@ struct _LinphoneChatMessage { LinphoneChatMessageStateChangedCb cb; void* cb_ud; void* message_userdata; + char* appdata; char* external_body_url; LinphoneAddress *from; LinphoneAddress *to; @@ -821,6 +822,7 @@ sqlite3 * linphone_message_storage_init(); void linphone_message_storage_init_chat_rooms(LinphoneCore *lc); #endif void linphone_chat_message_store_state(LinphoneChatMessage *msg); +void linphone_chat_message_store_appdata(LinphoneChatMessage* msg); void linphone_core_message_storage_init(LinphoneCore *lc); void linphone_core_message_storage_close(LinphoneCore *lc); void linphone_core_message_storage_set_debug(LinphoneCore *lc, bool_t debug); From 038bebdcc9e9ee55987de849c51ae816ca9d4290 Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Mon, 2 Jun 2014 14:15:49 +0200 Subject: [PATCH 50/94] Add a writable prefix variable for the tester. This will allow mobile unit tests to pass the message storage tests --- tester/liblinphone_tester.h | 2 ++ tester/message_tester.c | 2 +- tester/tester.c | 7 +++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index 152dd6dd3..de781c469 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -46,6 +46,7 @@ extern "C" { #endif extern const char *liblinphone_tester_file_prefix; +extern const char *liblinphone_tester_writable_dir_prefix; extern test_suite_t setup_test_suite; extern test_suite_t register_test_suite; extern test_suite_t call_test_suite; @@ -68,6 +69,7 @@ extern void liblinphone_tester_init(void); extern void liblinphone_tester_uninit(void); extern int liblinphone_tester_run_tests(const char *suite_name, const char *test_name); extern void liblinphone_tester_set_fileprefix(const char* file_prefix); +extern void liblinphone_tester_set_writable_dir_prefix(const char* writable_dir_prefix); #ifdef __cplusplus }; diff --git a/tester/message_tester.c b/tester/message_tester.c index 09872e565..37c79bbfe 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -453,7 +453,7 @@ static void message_storage_migration() { char src_db[256]; char tmp_db[256]; snprintf(src_db,sizeof(src_db), "%s/messages.db", liblinphone_tester_file_prefix); - snprintf(tmp_db,sizeof(tmp_db), "%s/tmp.db", liblinphone_tester_file_prefix); + snprintf(tmp_db,sizeof(tmp_db), "%s/tmp.db", liblinphone_tester_writable_dir_prefix); CU_ASSERT_EQUAL_FATAL(message_tester_copy_file(src_db, tmp_db), 1); diff --git a/tester/tester.c b/tester/tester.c index 3e09be635..da8552ab5 100644 --- a/tester/tester.c +++ b/tester/tester.c @@ -49,6 +49,9 @@ const char *liblinphone_tester_file_prefix="./app/native/assets/"; const char *liblinphone_tester_file_prefix="."; #endif +/* TODO: have the same "static" for QNX and windows as above? */ +const char *liblinphone_tester_writable_dir_prefix = "."; + const char *userhostsfile = "tester_hosts"; void liblinphone_tester_clock_start(MSTimeSpec *start){ @@ -326,6 +329,10 @@ void liblinphone_tester_set_fileprefix(const char* file_prefix){ liblinphone_tester_file_prefix = file_prefix; } +void liblinphone_tester_set_writable_dir_prefix(const char* writable_dir_prefix){ + liblinphone_tester_writable_dir_prefix = writable_dir_prefix; +} + void liblinphone_tester_init(void) { add_test_suite(&setup_test_suite); From 9a782d2d0c4113539e3e3c267c8f33106fc73e85 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 2 Jun 2014 17:20:46 +0200 Subject: [PATCH 51/94] Update oRTP and ms2 submodules. --- mediastreamer2 | 2 +- oRTP | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mediastreamer2 b/mediastreamer2 index 60b83538c..e8e16ee35 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 60b83538cd75a57703fd5267d40adc1b556f3245 +Subproject commit e8e16ee358ee103ba06fa5d1c99417b612504fe7 diff --git a/oRTP b/oRTP index 8c0d0ec22..eed961c93 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 8c0d0ec22ad4cac478584da6663be1409b1f9ec1 +Subproject commit eed961c934164b41af6d85cf0f40e22c14e97f2c From a5af301c136d25ab72b2100a10d16b9b0acef694 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Mon, 2 Jun 2014 17:33:22 +0200 Subject: [PATCH 52/94] fix memory leaks --- coreapi/bellesip_sal/sal_op_call.c | 3 +++ coreapi/linphonecall.c | 40 ++++++++++++++++++------------ coreapi/linphonecore.c | 9 +++++++ coreapi/proxy.c | 28 ++++++++++++--------- 4 files changed, 52 insertions(+), 28 deletions(-) diff --git a/coreapi/bellesip_sal/sal_op_call.c b/coreapi/bellesip_sal/sal_op_call.c index 2a433b7f6..e2e731510 100644 --- a/coreapi/bellesip_sal/sal_op_call.c +++ b/coreapi/bellesip_sal/sal_op_call.c @@ -154,6 +154,9 @@ static void handle_sdp_from_response(SalOp* op,belle_sip_response_t* response) { SalReason reason; if (extract_sdp(BELLE_SIP_MESSAGE(response),&sdp,&reason)==0) { if (sdp){ + if (op->base.remote_media){ + sal_media_description_unref(op->base.remote_media); + } op->base.remote_media=sal_media_description_new(); sdp_to_media_description(sdp,op->base.remote_media); if (op->base.local_media) sdp_process(op); diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 2bf68fc99..6861454d1 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -37,6 +37,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "mediastreamer2/mseventqueue.h" #include "mediastreamer2/mssndcard.h" +static void linphone_call_stats_uninit(LinphoneCallStats *stats); + #ifdef VIDEO_ENABLED static MSWebCam *get_nowebcam_device(){ return ms_web_cam_manager_get_cam(ms_web_cam_manager_get(),"StaticImage: Static picture"); @@ -743,7 +745,8 @@ static void linphone_call_set_terminated(LinphoneCall *call){ linphone_call_delete_upnp_session(call); linphone_call_delete_ice_session(call); linphone_core_update_allocated_audio_bandwidth(lc); - + linphone_call_stats_uninit(&call->stats[0]); + linphone_call_stats_uninit(&call->stats[1]); call->owns_call_log=FALSE; linphone_call_log_completed(call); @@ -2697,20 +2700,14 @@ static void report_bandwidth(LinphoneCall *call, MediaStream *as, MediaStream *v } static void linphone_core_disconnected(LinphoneCore *lc, LinphoneCall *call){ - char temp[256]; + char temp[256]={0}; char *from=NULL; - if(call) - from = linphone_call_get_remote_address_as_string(call); - if (from) - { - snprintf(temp,sizeof(temp),"Remote end %s seems to have disconnected, the call is going to be closed.",from); - ms_free(from); - } - else - { - snprintf(temp,sizeof(temp),"Remote end seems to have disconnected, the call is going to be closed."); - } - ms_message("On call [%p] %s",call,temp); + + 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 : ""); + if (from) ms_free(from); + + ms_message("On call [%p]: %s",call,temp); if (lc->vtable.display_warning!=NULL) lc->vtable.display_warning(lc,temp); linphone_core_terminate_call(lc,call); @@ -2818,6 +2815,17 @@ void linphone_call_stats_fill(LinphoneCallStats *stats, MediaStream *ms, OrtpEve } } +void linphone_call_stats_uninit(LinphoneCallStats *stats){ + if (stats->received_rtcp) { + freemsg(stats->received_rtcp); + stats->received_rtcp=NULL; + } + if (stats->sent_rtcp){ + freemsg(stats->sent_rtcp); + stats->sent_rtcp=NULL; + } +} + void linphone_call_notify_stats_updated(LinphoneCall *call, int stream_index){ LinphoneCallStats *stats=&call->stats[stream_index]; LinphoneCore *lc=call->core; @@ -2952,8 +2960,8 @@ void linphone_call_set_transfer_state(LinphoneCall* call, LinphoneCallState stat if (state != call->transfer_state) { LinphoneCore* lc = call->core; ms_message("Transfer state for call [%p] changed from [%s] to [%s]",call - ,linphone_call_state_to_string(call->transfer_state) - ,linphone_call_state_to_string(state)); + ,linphone_call_state_to_string(call->transfer_state) + ,linphone_call_state_to_string(state)); call->transfer_state = state; if (lc->vtable.transfer_state_changed) lc->vtable.transfer_state_changed(lc, call, state); diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index d593a1dbd..609b0bf2a 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -5890,6 +5890,15 @@ static void linphone_core_uninit(LinphoneCore *lc) } #endif //BUILD_UPNP + if (lc->chatrooms){ + MSList *cr=ms_list_copy(lc->chatrooms); + MSList *elem; + for(elem=cr;elem!=NULL;elem=elem->next){ + linphone_chat_room_destroy((LinphoneChatRoom*)elem->data); + } + ms_list_free(cr); + } + if (lp_config_needs_commit(lc->config)) lp_config_sync(lc->config); lp_config_destroy(lc->config); lc->config = NULL; /* Mark the config as NULL to block further calls */ diff --git a/coreapi/proxy.c b/coreapi/proxy.c index c85cc33dd..50ede392b 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -49,10 +49,7 @@ bool_t linphone_proxy_config_address_equal(const LinphoneAddress *a, const Linph if (linphone_address_weak_equal(a,b)) { /*also check both transport and uri */ - if (!(linphone_address_is_secure(a) ^ linphone_address_is_secure(b))) { - return linphone_address_get_transport(a) == linphone_address_get_transport(b); - } else - return FALSE; /*secure flag not equals*/ + return linphone_address_is_secure(a) == linphone_address_is_secure(b) && linphone_address_get_transport(a) == linphone_address_get_transport(b); } else return FALSE; /*either username, domain or port ar not equals*/ @@ -61,14 +58,21 @@ bool_t linphone_proxy_config_address_equal(const LinphoneAddress *a, const Linph bool_t linphone_proxy_config_is_server_config_changed(const LinphoneProxyConfig* obj) { LinphoneAddress *current_identity=obj->reg_identity?linphone_address_new(obj->reg_identity):NULL; LinphoneAddress *current_proxy=obj->reg_proxy?linphone_address_new(obj->reg_proxy):NULL; + bool_t result=FALSE; + + if (!linphone_proxy_config_address_equal(obj->saved_identity,current_identity)){ + result=TRUE; + goto end; + } + if (!linphone_proxy_config_address_equal(obj->saved_proxy,current_proxy)){ + result=TRUE; + goto end; + } - if (!linphone_proxy_config_address_equal(obj->saved_identity,current_identity)) - return TRUE; - - if (!linphone_proxy_config_address_equal(obj->saved_proxy,current_proxy)) - return TRUE; - - return FALSE; + end: + if (current_identity) linphone_address_destroy(current_identity); + if (current_proxy) linphone_address_destroy(current_proxy); + return result; } void linphone_proxy_config_write_all_to_config_file(LinphoneCore *lc){ @@ -151,7 +155,7 @@ void linphone_proxy_config_destroy(LinphoneProxyConfig *obj){ if (obj->contact_params) ms_free(obj->contact_params); if (obj->contact_uri_params) ms_free(obj->contact_uri_params); if (obj->saved_proxy!=NULL) linphone_address_destroy(obj->saved_proxy); - if (obj->saved_identity!=NULL) ms_free(obj->saved_identity); + if (obj->saved_identity!=NULL) linphone_address_destroy(obj->saved_identity); ms_free(obj); } From ba5c902bba08be1eec58f2f55ef44efdaa50aa75 Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Mon, 2 Jun 2014 15:09:08 +0200 Subject: [PATCH 53/94] add option sip_update to linphonerc to disable SIP UPDATE --- coreapi/bellesip_sal/sal_impl.c | 4 ++++ coreapi/bellesip_sal/sal_impl.h | 1 + coreapi/bellesip_sal/sal_op_call.c | 18 ++++++++++++------ coreapi/linphonecore.c | 1 + include/sal/sal.h | 3 +++ 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/coreapi/bellesip_sal/sal_impl.c b/coreapi/bellesip_sal/sal_impl.c index 0212c8a39..c5040be3a 100644 --- a/coreapi/bellesip_sal/sal_impl.c +++ b/coreapi/bellesip_sal/sal_impl.c @@ -451,6 +451,7 @@ Sal * sal_init(){ sal->tls_verify=TRUE; sal->tls_verify_cn=TRUE; sal->refresher_retry_after=60000; /*default value in ms*/ + sal->enable_sip_update=TRUE; return sal; } @@ -999,4 +1000,7 @@ void sal_cancel_timer(Sal *sal, belle_sip_source_t *timer) { belle_sip_main_loop_t *ml = belle_sip_stack_get_main_loop(sal->stack); belle_sip_main_loop_remove_source(ml, timer); } +void sal_enable_sip_update_method(Sal *ctx,bool_t value) { + ctx->enable_sip_update=value; +} diff --git a/coreapi/bellesip_sal/sal_impl.h b/coreapi/bellesip_sal/sal_impl.h index b6e18d76a..a20150de2 100644 --- a/coreapi/bellesip_sal/sal_impl.h +++ b/coreapi/bellesip_sal/sal_impl.h @@ -48,6 +48,7 @@ struct Sal{ bool_t auto_contacts; bool_t enable_test_features; bool_t no_initial_route; + bool_t enable_sip_update; /*true by default*/ }; typedef enum SalOpState { diff --git a/coreapi/bellesip_sal/sal_op_call.c b/coreapi/bellesip_sal/sal_op_call.c index e2e731510..59473474f 100644 --- a/coreapi/bellesip_sal/sal_op_call.c +++ b/coreapi/bellesip_sal/sal_op_call.c @@ -564,6 +564,9 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t } 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 * ... @@ -571,8 +574,9 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t * the request with a 504 response. */ resp=sal_op_create_response_from_request(op,req,504); - 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_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{ @@ -610,14 +614,16 @@ int sal_call_set_local_media_description(SalOp *op, SalMediaDescription *desc){ return 0; } -static belle_sip_header_allow_t *create_allow(){ +static belle_sip_header_allow_t *create_allow(bool_t enable_update){ belle_sip_header_allow_t* header_allow; - header_allow = belle_sip_header_allow_create("INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO, UPDATE"); + char allow [256]; + snprintf(allow,sizeof(allow),"INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO%s",(enable_update?", UPDATE":"")); + header_allow = belle_sip_header_allow_create(allow); return header_allow; } static void sal_op_fill_invite(SalOp *op, belle_sip_request_t* invite) { - belle_sip_message_add_header(BELLE_SIP_MESSAGE(invite),BELLE_SIP_HEADER(create_allow())); + belle_sip_message_add_header(BELLE_SIP_MESSAGE(invite),BELLE_SIP_HEADER(create_allow(op->base.root->enable_sip_update))); if (op->base.root->session_expires!=0){ belle_sip_message_add_header(BELLE_SIP_MESSAGE(invite),belle_sip_header_create( "Session-expires", "200")); @@ -745,7 +751,7 @@ int sal_call_accept(SalOp*h){ ms_error("Fail to build answer for call"); return -1; } - belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),BELLE_SIP_HEADER(create_allow())); + belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),BELLE_SIP_HEADER(create_allow(h->base.root->enable_sip_update))); if (h->base.root->session_expires!=0){ if (h->supports_session_timers) { belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),belle_sip_header_create("Supported", "timer")); diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 609b0bf2a..582633783 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -754,6 +754,7 @@ static void sip_config_read(LinphoneCore *lc) linphone_core_enable_keep_alive(lc, (lc->sip_conf.keepalive_period > 0)); sal_use_one_matching_codec_policy(lc->sal,lp_config_get_int(lc->config,"sip","only_one_codec",0)); sal_use_dates(lc->sal,lp_config_get_int(lc->config,"sip","put_date",0)); + sal_enable_sip_update_method(lc->sal,lp_config_get_int(lc->config,"sip","sip_update",1)); } static void rtp_config_read(LinphoneCore *lc) diff --git a/include/sal/sal.h b/include/sal/sal.h index d5712c09d..5ec012795 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -519,6 +519,9 @@ void sal_set_keepalive_period(Sal *ctx,unsigned int value); void sal_use_tcp_tls_keepalive(Sal *ctx, bool_t enabled); int sal_enable_tunnel(Sal *ctx, void *tunnelclient); void sal_disable_tunnel(Sal *ctx); +/*Default value is true*/ +void sal_enable_sip_update_method(Sal *ctx,bool_t value); + /** * returns keepalive period in ms * 0 desactiaved From 419cac616c5e8b28117da576109fa91222b9ab5f Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 3 Jun 2014 12:22:03 +0200 Subject: [PATCH 54/94] Fix memory leak in presence model. --- coreapi/presence.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/coreapi/presence.c b/coreapi/presence.c index fa4b97014..df9271844 100644 --- a/coreapi/presence.c +++ b/coreapi/presence.c @@ -278,6 +278,7 @@ LinphonePresenceBasicStatus linphone_presence_model_get_basic_status(const Linph int linphone_presence_model_set_basic_status(LinphonePresenceModel *model, LinphonePresenceBasicStatus basic_status) { LinphonePresenceService *service; + int err = 0; if (model == NULL) return -1; @@ -285,8 +286,9 @@ int linphone_presence_model_set_basic_status(LinphonePresenceModel *model, Linph service = linphone_presence_service_new(NULL, basic_status, NULL); if (service == NULL) return -1; - if (linphone_presence_model_add_service(model, service) < 0) return -1; - return 0; + err = linphone_presence_model_add_service(model, service); + linphone_presence_service_unref(service); + return err; } static void presence_service_find_newer_timestamp(LinphonePresenceService *service, time_t *timestamp) { @@ -363,6 +365,7 @@ LinphonePresenceActivity * linphone_presence_model_get_activity(const LinphonePr int linphone_presence_model_set_activity(LinphonePresenceModel *model, LinphonePresenceActivityType acttype, const char *description) { LinphonePresenceBasicStatus basic_status = LinphonePresenceBasicStatusOpen; LinphonePresenceActivity *activity; + int err = 0; if (model == NULL) return -1; @@ -383,8 +386,9 @@ int linphone_presence_model_set_activity(LinphonePresenceModel *model, LinphoneP linphone_presence_model_clear_activities(model); activity = linphone_presence_activity_new(acttype, description); if (activity == NULL) return -1; - return linphone_presence_model_add_activity(model, activity); - + err = linphone_presence_model_add_activity(model, activity); + linphone_presence_activity_unref(activity); + return err; } unsigned int linphone_presence_model_get_nb_activities(const LinphonePresenceModel *model) { From 19e8cea754496ad6c86e901e8e431b63d58f7cf4 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 3 Jun 2014 14:43:05 +0200 Subject: [PATCH 55/94] Update oRTP and ms2 submodules. --- mediastreamer2 | 2 +- oRTP | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mediastreamer2 b/mediastreamer2 index e8e16ee35..d2d96d8d9 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit e8e16ee358ee103ba06fa5d1c99417b612504fe7 +Subproject commit d2d96d8d9c499477c903ac42cb0b77de3dfd56e3 diff --git a/oRTP b/oRTP index eed961c93..aa1ab4e8c 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit eed961c934164b41af6d85cf0f40e22c14e97f2c +Subproject commit aa1ab4e8cd367e51c9706bfe4947a744efe06412 From f11f588729ad8e885bc5aecc2513a5326ccfbb58 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 3 Jun 2014 14:47:55 +0200 Subject: [PATCH 56/94] Add JNI wrapper for AVPF parameters of the proxy config. --- coreapi/linphonecore_jni.cc | 12 ++++++++++++ .../org/linphone/core/LinphoneProxyConfig.java | 18 ++++++++++++++++++ .../linphone/core/LinphoneProxyConfigImpl.java | 18 ++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index 4b04cb387..e1f2da371 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -2944,6 +2944,18 @@ extern "C" jint Java_org_linphone_core_LinphoneProxyConfigImpl_getPrivacy(JNIEnv return linphone_proxy_config_get_privacy((LinphoneProxyConfig *) ptr); } +JNIEXPORT void JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_enableAvpf(JNIEnv *env, jobject thiz, jlong ptr, jboolean enable) { + linphone_proxy_config_enable_avpf((LinphoneProxyConfig *)ptr, (bool)enable); +} + +JNIEXPORT void JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_setAvpfRRInterval(JNIEnv *env, jobject thiz, jlong ptr, jint interval) { + linphone_proxy_config_set_avpf_rr_interval((LinphoneProxyConfig *)ptr, (uint8_t)interval); +} + +JNIEXPORT jint JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_getAvpfRRInterval(JNIEnv *env, jobject thiz, jlong ptr) { + return (jint)linphone_proxy_config_get_avpf_rr_interval((LinphoneProxyConfig *)ptr); +} + extern "C" jint Java_org_linphone_core_LinphoneCallImpl_getDuration(JNIEnv* env,jobject thiz,jlong ptr) { return (jint)linphone_call_get_duration((LinphoneCall *) ptr); } diff --git a/java/common/org/linphone/core/LinphoneProxyConfig.java b/java/common/org/linphone/core/LinphoneProxyConfig.java index 0ff1900a3..b695e08f7 100644 --- a/java/common/org/linphone/core/LinphoneProxyConfig.java +++ b/java/common/org/linphone/core/LinphoneProxyConfig.java @@ -163,6 +163,24 @@ public interface LinphoneProxyConfig { */ int getPrivacy(); + /** + * Indicates whether AVPF/SAVPF must be used for calls using this proxy config. + * @param enable True to enable AVPF/SAVF, false to disable it. + */ + void enableAvpf(boolean enable); + + /** + * Set the interval between regular RTCP reports when using AVPF/SAVPF. + * @param interval The interval in seconds (between 0 and 5 seconds). + */ + void setAvpfRRInterval(int interval); + + /** + * Get the interval between regular RTCP reports when using AVPF/SAVPF. + * @return The interval in seconds. + */ + int getAvpfRRInterval(); + /** * Set optional contact parameters that will be added to the contact information sent in the registration. diff --git a/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java b/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java index a78819075..53db2a526 100644 --- a/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java +++ b/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java @@ -198,6 +198,24 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { return getPrivacy(nativePtr); } + private native void enableAvpf(long nativePtr, boolean enable); + @Override + public void enableAvpf(boolean enable) { + enableAvpf(nativePtr, enable); + } + + private native void setAvpfRRInterval(long nativePtr, int interval); + @Override + public void setAvpfRRInterval(int interval) { + setAvpfRRInterval(nativePtr, interval); + } + + private native int getAvpfRRInterval(long nativePtr); + @Override + public int getAvpfRRInterval() { + return getAvpfRRInterval(nativePtr); + } + private native String getContactParameters(long ptr); @Override public String getContactParameters() { From 2498e3164a59283db6f2125b0a33b885d4c6a2f7 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 3 Jun 2014 15:05:37 +0200 Subject: [PATCH 57/94] Initialize avpf parameters of proxy config with default values if available. --- coreapi/proxy.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/coreapi/proxy.c b/coreapi/proxy.c index 50ede392b..25696691d 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -112,6 +112,8 @@ static void linphone_proxy_config_init(LinphoneCore* lc, LinphoneProxyConfig *ob obj->send_statistics = lc ? lp_config_get_default_int(lc->config, "proxy", "send_statistics", 0) : 0; obj->contact_params = contact_params ? ms_strdup(contact_params) : NULL; obj->contact_uri_params = contact_uri_params ? ms_strdup(contact_uri_params) : NULL; + obj->avpf_enabled = lc ? lp_config_get_default_int(lc->config, "proxy", "avpf", 0) : 0; + obj->avpf_rr_interval = lc ? lp_config_get_default_int(lc->config, "proxy", "avpf_rr_interval", 5) : 5; obj->publish_expires=-1; } @@ -1236,7 +1238,7 @@ LinphoneProxyConfig *linphone_proxy_config_new_from_config_file(LpConfig *config linphone_proxy_config_enable_publish(cfg,lp_config_get_int(config,key,"publish",0)); linphone_proxy_config_enable_avpf(cfg, lp_config_get_int(config, key, "avpf", 0)); - linphone_proxy_config_set_avpf_rr_interval(cfg, lp_config_get_int(config, key, "avpf_rr_interval", 0)); + linphone_proxy_config_set_avpf_rr_interval(cfg, lp_config_get_int(config, key, "avpf_rr_interval", 5)); linphone_proxy_config_set_dial_escape_plus(cfg,lp_config_get_int(config,key,"dial_escape_plus",lp_config_get_default_int(config,"proxy","dial_escape_plus",0))); linphone_proxy_config_set_dial_prefix(cfg,lp_config_get_string(config,key,"dial_prefix",lp_config_get_default_string(config,"proxy","dial_prefix",NULL))); From 706d9c3ed233509dc7f8e2756fa63e160eb6f0b2 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Tue, 3 Jun 2014 17:34:03 +0200 Subject: [PATCH 58/94] fix bug enabling avpf accidentally --- coreapi/linphonecore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 582633783..d858839c7 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -6448,7 +6448,7 @@ void linphone_core_init_default_params(LinphoneCore*lc, LinphoneCallParams *para params->media_encryption=linphone_core_get_media_encryption(lc); params->in_conference=FALSE; params->privacy=LinphonePrivacyDefault; - params->avpf_enabled=TRUE; + params->avpf_enabled=FALSE; } void linphone_core_set_device_identifier(LinphoneCore *lc,const char* device_id) { From ba0a032433d36c72adc8f664e8d9c1bc7ae1c94f Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 3 Jun 2014 17:41:44 +0200 Subject: [PATCH 59/94] Update oRTP and ms2 submodules. --- mediastreamer2 | 2 +- oRTP | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mediastreamer2 b/mediastreamer2 index d2d96d8d9..f813759ed 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit d2d96d8d9c499477c903ac42cb0b77de3dfd56e3 +Subproject commit f813759edf71ec3d7b1388ae772522df81d1d628 diff --git a/oRTP b/oRTP index aa1ab4e8c..ae0fec375 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit aa1ab4e8cd367e51c9706bfe4947a744efe06412 +Subproject commit ae0fec37506f4cc5de7dfc8ca84321b7ae311bbe From dafdbb34446c7b3c8bfd2a259f5acf12051a3273 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 4 Jun 2014 11:59:50 +0200 Subject: [PATCH 60/94] Correctly handle negotiation of RTP profiles (APV/APVF/SAVP/SAVPF). --- coreapi/linphonecall.c | 18 ++++++++++++------ coreapi/linphonecore.c | 24 +++++++++--------------- coreapi/misc.c | 26 ++++++++++++++++++++++++-- coreapi/offeranswer.c | 10 ++++------ coreapi/private.h | 6 ++++-- 5 files changed, 53 insertions(+), 31 deletions(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 6861454d1..d80a76920 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -266,8 +266,8 @@ static void setup_encryption_keys(LinphoneCall *call, SalMediaDescription *md){ bool_t keep_srtp_keys=lp_config_get_int(lc->config,"sip","keep_srtp_keys",1); for(i=0; in_active_streams; i++) { - if (is_encryption_active(&md->streams[i]) == TRUE) { - if (keep_srtp_keys && old_md && is_encryption_active(&old_md->streams[i]) == TRUE){ + if (stream_description_has_srtp(&md->streams[i]) == TRUE) { + if (keep_srtp_keys && old_md && stream_description_has_srtp(&old_md->streams[i]) == TRUE){ int j; ms_message("Keeping same crypto keys."); for(j=0;jparams.has_video &= linphone_core_media_description_contains_video_stream(md); + + /* Handle AVPF and SRTP. */ + call->params.avpf_enabled = media_description_has_avpf(md); + if ((media_description_has_srtp(md) == TRUE) && (media_stream_srtp_supported() == TRUE)) { + call->params.media_encryption = LinphoneMediaEncryptionSRTP; + } } fpol=linphone_core_get_firewall_policy(call->core); /*create the ice session now if ICE is required*/ @@ -1015,11 +1021,11 @@ const LinphoneCallParams * linphone_call_get_remote_params(LinphoneCall *call){ for (i = 0; i < nb_video_streams; i++) { sd = sal_media_description_get_active_stream_of_type(md, SalVideo, i); if (is_video_active(sd) == TRUE) cp->has_video = TRUE; - if (is_encryption_active(sd) == TRUE) cp->media_encryption = LinphoneMediaEncryptionSRTP; + if (stream_description_has_srtp(sd) == TRUE) cp->media_encryption = LinphoneMediaEncryptionSRTP; } for (i = 0; i < nb_audio_streams; i++) { sd = sal_media_description_get_active_stream_of_type(md, SalAudio, i); - if (is_encryption_active(sd) == TRUE) cp->media_encryption = LinphoneMediaEncryptionSRTP; + if (stream_description_has_srtp(sd) == TRUE) cp->media_encryption = LinphoneMediaEncryptionSRTP; } if (!cp->has_video){ if (md->bandwidth>0 && md->bandwidth<=linphone_core_get_edge_bw(call->core)){ @@ -1992,7 +1998,7 @@ static void linphone_call_start_audio_stream(LinphoneCall *call, const char *cna call->current_params.record_file=ms_strdup(call->params.record_file); } /* valid local tags are > 0 */ - if (is_encryption_active(stream) == TRUE) { + if (stream_description_has_srtp(stream) == TRUE) { local_st_desc=sal_media_description_find_stream(call->localdesc,stream->proto,SalAudio); crypto_idx = find_crypto_index_from_tag(local_st_desc->crypto, stream->crypto_local_tag); @@ -2109,7 +2115,7 @@ static void linphone_call_start_video_stream(LinphoneCall *call, const char *cna cam=get_nowebcam_device(); } if (!is_inactive){ - if (is_encryption_active(vstream) == TRUE) { + if (stream_description_has_srtp(vstream) == TRUE) { int crypto_idx = find_crypto_index_from_tag(local_st_desc->crypto, vstream->crypto_local_tag); if (crypto_idx >= 0) { media_stream_set_srtp_recv_key(&call->videostream->ms,vstream->crypto[0].algo,vstream->crypto[0].master_key); diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index d858839c7..f51453475 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -2973,21 +2973,8 @@ bool_t linphone_core_inc_invite_pending(LinphoneCore*lc){ return FALSE; } -bool_t linphone_core_media_description_has_srtp(const SalMediaDescription *md){ - int i; - if (md->n_active_streams==0) return FALSE; - - for(i=0;in_active_streams;i++){ - const SalStreamDescription *sd=&md->streams[i]; - if (is_encryption_active(sd) != TRUE){ - return FALSE; - } - } - return TRUE; -} - bool_t linphone_core_incompatible_security(LinphoneCore *lc, SalMediaDescription *md){ - return linphone_core_is_media_encryption_mandatory(lc) && linphone_core_get_media_encryption(lc)==LinphoneMediaEncryptionSRTP && !linphone_core_media_description_has_srtp(md); + return linphone_core_is_media_encryption_mandatory(lc) && linphone_core_get_media_encryption(lc)==LinphoneMediaEncryptionSRTP && !media_description_has_srtp(md); } void linphone_core_notify_incoming_call(LinphoneCore *lc, LinphoneCall *call){ @@ -3432,7 +3419,14 @@ int linphone_core_accept_call_with_params(LinphoneCore *lc, LinphoneCall *call, _linphone_call_params_copy(&call->params,params); // There might not be a md if the INVITE was lacking an SDP // In this case we use the parameters as is. - if (md) call->params.has_video &= linphone_core_media_description_contains_video_stream(md); + if (md) { + call->params.has_video &= linphone_core_media_description_contains_video_stream(md); + /* Handle AVPF and SRTP. */ + call->params.avpf_enabled = media_description_has_avpf(md); + if ((media_description_has_srtp(md) == TRUE) && (media_stream_srtp_supported() == TRUE)) { + call->params.media_encryption = LinphoneMediaEncryptionSRTP; + } + } linphone_call_make_local_media_description(lc,call); sal_call_set_local_media_description(call->op,call->localdesc); sal_op_set_sent_custom_header(call->op,params->custom_headers); diff --git a/coreapi/misc.c b/coreapi/misc.c index f8fae4210..57ebaa32e 100644 --- a/coreapi/misc.c +++ b/coreapi/misc.c @@ -1524,6 +1524,28 @@ bool_t is_video_active(const SalStreamDescription *sd) { return (sd->rtp_port != 0) && (sd->dir != SalStreamInactive); } -bool_t is_encryption_active(const SalStreamDescription *sd) { - return ((sd->proto == SalProtoRtpSavpf) || (sd->proto == SalProtoRtpSavp)); +bool_t stream_description_has_avpf(const SalStreamDescription *sd) { + return ((sd->proto == SalProtoRtpAvpf) || (sd->proto == SalProtoRtpSavpf)); +} + +bool_t stream_description_has_srtp(const SalStreamDescription *sd) { + return ((sd->proto == SalProtoRtpSavp) || (sd->proto == SalProtoRtpSavpf)); +} + +bool_t media_description_has_avpf(const SalMediaDescription *md) { + int i; + if (md->n_active_streams == 0) return FALSE; + for (i = 0; i < md->n_active_streams; i++) { + if (stream_description_has_avpf(&md->streams[i]) != TRUE) return FALSE; + } + return TRUE; +} + +bool_t media_description_has_srtp(const SalMediaDescription *md) { + int i; + if (md->n_active_streams == 0) return FALSE; + for (i = 0; i < md->n_active_streams; i++) { + if (stream_description_has_srtp(&md->streams[i]) != TRUE) return FALSE; + } + return TRUE; } diff --git a/coreapi/offeranswer.c b/coreapi/offeranswer.c index 2d89edcf7..aef918b92 100644 --- a/coreapi/offeranswer.c +++ b/coreapi/offeranswer.c @@ -237,7 +237,7 @@ static void initiate_outgoing(const SalStreamDescription *local_offer, }else{ result->rtp_port=0; } - if (is_encryption_active(result) == TRUE) { + if (stream_description_has_srtp(result) == TRUE) { /* verify crypto algo */ memset(result->crypto, 0, sizeof(result->crypto)); if (!match_crypto_algo(local_offer->crypto, remote_answer->crypto, &result->crypto[0], &result->crypto_local_tag, FALSE)) @@ -263,7 +263,7 @@ static void initiate_incoming(const SalStreamDescription *local_cap, }else{ result->rtp_port=0; } - if (is_encryption_active(result) == TRUE) { + if (stream_description_has_srtp(result) == TRUE) { /* select crypto algo */ memset(result->crypto, 0, sizeof(result->crypto)); if (!match_crypto_algo(local_cap->crypto, remote_offer->crypto, &result->crypto[0], &result->crypto_local_tag, TRUE)) @@ -330,10 +330,8 @@ static bool_t local_stream_not_already_used(const SalMediaDescription *result, c /*in answering mode, we consider that if we are able to make AVPF/SAVP/SAVPF, then we can do AVP as well*/ static bool_t proto_compatible(SalMediaProto local, SalMediaProto remote) { if (local == remote) return TRUE; - if ((remote == SalProtoRtpAvpf) && (local == SalProtoRtpSavpf)) - return TRUE; - if ((remote == SalProtoRtpAvp) && ((local == SalProtoRtpSavp) || (local == SalProtoRtpAvpf) || (local == SalProtoRtpSavpf))) - return TRUE; + if ((remote == SalProtoRtpAvp) && ((local == SalProtoRtpSavp) || (local == SalProtoRtpSavpf))) return TRUE; + if ((remote == SalProtoRtpAvpf) && (local == SalProtoRtpSavpf)) return TRUE; return FALSE; } diff --git a/coreapi/private.h b/coreapi/private.h index ac93f41b9..8de760b85 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -337,7 +337,6 @@ void linphone_call_stats_fill(LinphoneCallStats *stats, MediaStream *ms, OrtpEve void linphone_core_update_local_media_description_from_ice(SalMediaDescription *desc, IceSession *session); void linphone_core_update_ice_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md); bool_t linphone_core_media_description_contains_video_stream(const SalMediaDescription *md); -bool_t linphone_core_media_description_has_srtp(const SalMediaDescription *md); void linphone_core_send_initial_subscribes(LinphoneCore *lc); void linphone_core_write_friends_config(LinphoneCore* lc); @@ -393,7 +392,10 @@ bool_t linphone_core_rtcp_enabled(const LinphoneCore *lc); LinphoneCall * is_a_linphone_call(void *user_pointer); LinphoneProxyConfig * is_a_linphone_proxy_config(void *user_pointer); bool_t is_video_active(const SalStreamDescription *sd); -bool_t is_encryption_active(const SalStreamDescription *sd); +bool_t stream_description_has_avpf(const SalStreamDescription *sd); +bool_t stream_description_has_srtp(const SalStreamDescription *sd); +bool_t media_description_has_avpf(const SalMediaDescription *md); +bool_t media_description_has_srtp(const SalMediaDescription *md); void linphone_core_queue_task(LinphoneCore *lc, belle_sip_source_func_t task_fun, void *data, const char *task_description); From 85df75c1193e5867b1debd5a5e2465b12b979da1 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 4 Jun 2014 14:48:40 +0200 Subject: [PATCH 61/94] Update ms2 submodule. --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index f813759ed..916b6465a 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit f813759edf71ec3d7b1388ae772522df81d1d628 +Subproject commit 916b6465a2f46883cb2ad17ccc7b8fc210f1a942 From 10c9de93cadcf466dc46101104f77bbdc9273ec3 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Wed, 4 Jun 2014 15:15:46 +0200 Subject: [PATCH 62/94] implement early media forking at client side --- coreapi/bellesip_sal/sal_op_call.c | 28 ++++++---- coreapi/bellesip_sal/sal_op_impl.c | 32 +++++++----- coreapi/callbacks.c | 64 ++++++++++++++++++++++- mediastreamer2 | 2 +- oRTP | 2 +- tester/call_tester.c | 84 +++++++++++++++++++++++++++++- tester/rcfiles/marie_early_rc | 4 +- 7 files changed, 187 insertions(+), 29 deletions(-) diff --git a/coreapi/bellesip_sal/sal_op_call.c b/coreapi/bellesip_sal/sal_op_call.c index 59473474f..f8d35e15c 100644 --- a/coreapi/bellesip_sal/sal_op_call.c +++ b/coreapi/bellesip_sal/sal_op_call.c @@ -152,11 +152,12 @@ static void process_dialog_terminated(void *ctx, const belle_sip_dialog_terminat static void handle_sdp_from_response(SalOp* op,belle_sip_response_t* response) { belle_sdp_session_description_t* sdp; SalReason reason; + if (op->base.remote_media){ + sal_media_description_unref(op->base.remote_media); + op->base.remote_media=NULL; + } if (extract_sdp(BELLE_SIP_MESSAGE(response),&sdp,&reason)==0) { if (sdp){ - if (op->base.remote_media){ - sal_media_description_unref(op->base.remote_media); - } op->base.remote_media=sal_media_description_new(); sdp_to_media_description(sdp,op->base.remote_media); if (op->base.local_media) sdp_process(op); @@ -177,6 +178,7 @@ static int vfu_retry (void *user_data, unsigned int events) { sal_op_unref(op); return BELLE_SIP_STOP; } + static void call_process_response(void *op_base, const belle_sip_response_event_t *event){ SalOp* op = (SalOp*)op_base; belle_sip_request_t* ack; @@ -186,17 +188,17 @@ static void call_process_response(void *op_base, const belle_sip_response_event_ belle_sip_response_t* response=belle_sip_response_event_get_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); if (!client_transaction) { ms_warning("Discarding stateless response [%i] on op [%p]",code,op); return; } req=belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(client_transaction)); - set_or_update_dialog(op,belle_sip_response_event_get_dialog(event)); - dialog_state=op->dialog?belle_sip_dialog_get_state(op->dialog):BELLE_SIP_DIALOG_NULL; + set_or_update_dialog(op,dialog); + dialog_state=dialog ? belle_sip_dialog_get_state(dialog) : BELLE_SIP_DIALOG_NULL; - ms_message("Op [%p] receiving call response [%i], dialog is [%p] in state [%s]",op,code,op->dialog,belle_sip_dialog_state_to_string(dialog_state)); + 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: @@ -218,14 +220,18 @@ static void call_process_response(void *op_base, const belle_sip_response_event_ if (op->dialog==NULL) call_set_released(op); } } - } else if (code >= 180 && code<300) { - handle_sdp_from_response(op,response); - op->base.root->callbacks.call_ringing(op); + } else if (code >= 180 && code<200) { + belle_sip_response_t *prev_response=belle_sip_object_data_get(BELLE_SIP_OBJECT(dialog),"early_response"); + if (!prev_response || code>belle_sip_response_get_status_code(prev_response)){ + handle_sdp_from_response(op,response); + op->base.root->callbacks.call_ringing(op); + } + belle_sip_object_data_set(BELLE_SIP_OBJECT(dialog),"early_response",belle_sip_object_ref(response),belle_sip_object_unref); } else if (code>=300){ call_set_error(op,response); if (op->dialog==NULL) call_set_released(op); } - } else if ( code >=200 + } else if (code >=200 && code<300 && strcmp("UPDATE",belle_sip_request_get_method(req))==0) { handle_sdp_from_response(op,response); diff --git a/coreapi/bellesip_sal/sal_op_impl.c b/coreapi/bellesip_sal/sal_op_impl.c index a364ce324..e2678dde0 100644 --- a/coreapi/bellesip_sal/sal_op_impl.c +++ b/coreapi/bellesip_sal/sal_op_impl.c @@ -560,20 +560,28 @@ const SalErrorInfo *sal_op_get_error_info(const SalOp *op){ return &op->error_info; } +static void unlink_op_with_dialog(SalOp *op, belle_sip_dialog_t* dialog){ + belle_sip_dialog_set_application_data(dialog,NULL); + sal_op_unref(op); + belle_sip_object_unref(dialog); +} + +static belle_sip_dialog_t *link_op_with_dialog(SalOp *op, belle_sip_dialog_t* dialog){ + belle_sip_dialog_set_application_data(dialog,sal_op_ref(op)); + belle_sip_object_ref(dialog); + return dialog; +} + void set_or_update_dialog(SalOp* op, belle_sip_dialog_t* dialog) { - /*check if dialog has changed*/ - if (dialog && dialog != op->dialog) { - ms_message("Dialog set from [%p] to [%p] for op [%p]",op->dialog,dialog,op); - /*fixme, shouldn't we cancel previous dialog*/ - if (op->dialog) { - belle_sip_dialog_set_application_data(op->dialog,NULL); - belle_sip_object_unref(op->dialog); - sal_op_unref(op); + if (dialog==NULL) return; + ms_message("op [%p] : set_or_update_dialog() current=[%p] new=[%p]",op,op->dialog,dialog); + if (dialog && op->dialog!=dialog){ + if (op->dialog){ + /*FIXME: shouldn't we delete unconfirmed dialogs ?*/ + unlink_op_with_dialog(op,op->dialog); + op->dialog=NULL; } - op->dialog=dialog; - belle_sip_dialog_set_application_data(op->dialog,op); - sal_op_ref(op); - belle_sip_object_ref(op->dialog); + op->dialog=link_op_with_dialog(op,dialog); } } /*return reffed op*/ diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index 64f17f411..9b427506b 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -72,6 +72,32 @@ void linphone_core_update_streams_destinations(LinphoneCore *lc, LinphoneCall *c #endif } +static void _clear_early_media_destinations(LinphoneCall *call, MediaStream *ms){ + RtpSession *session=ms->sessions.rtp_session; + rtp_session_clear_aux_remote_addr(session); + if (!call->ice_session) rtp_session_set_symmetric_rtp(session,TRUE);/*restore symmetric rtp if ICE is not used*/ +} + +static void clear_early_media_destinations(LinphoneCall *call){ + if (call->audiostream){ + _clear_early_media_destinations(call,(MediaStream*)call->audiostream); + } + if (call->videostream){ + _clear_early_media_destinations(call,(MediaStream*)call->videostream); + } +} + +static void prepare_early_media_forking(LinphoneCall *call){ + /*we need to disable symmetric rtp otherwise our outgoing streams will be switching permanently between the multiple destinations*/ + if (call->audiostream){ + rtp_session_set_symmetric_rtp(call->audiostream->ms.sessions.rtp_session,FALSE); + } + if (call->videostream){ + rtp_session_set_symmetric_rtp(call->videostream->ms.sessions.rtp_session,FALSE); + } + +} + void linphone_core_update_streams(LinphoneCore *lc, LinphoneCall *call, SalMediaDescription *new_md){ SalMediaDescription *oldmd=call->resultdesc; bool_t all_muted=FALSE; @@ -98,6 +124,7 @@ void linphone_core_update_streams(LinphoneCore *lc, LinphoneCall *call, SalMedia call->expect_media_in_ack=FALSE; call->resultdesc=new_md; if ((call->audiostream && call->audiostream->ms.state==MSStreamStarted) || (call->videostream && call->videostream->ms.state==MSStreamStarted)){ + clear_early_media_destinations(call); /* we already started media: check if we really need to restart it*/ if (oldmd){ int md_changed = media_parameters_changed(call, oldmd, new_md); @@ -146,6 +173,9 @@ void linphone_core_update_streams(LinphoneCore *lc, LinphoneCall *call, SalMedia if ((call->state==LinphoneCallIncomingEarlyMedia || call->state==LinphoneCallOutgoingEarlyMedia) && !call->params.real_early_media){ all_muted=TRUE; } + if (call->params.real_early_media && call->state==LinphoneCallOutgoingEarlyMedia){ + prepare_early_media_forking(call); + } linphone_call_start_media_streams(call,all_muted,send_ringbacktone); if (call->state==LinphoneCallPausing && call->paused_by_app && ms_list_size(lc->calls)==1){ linphone_core_play_named_tone(lc,LinphoneToneCallOnHold); @@ -276,6 +306,38 @@ static void call_received(SalOp *h){ linphone_core_notify_incoming_call(lc,call); } +static void try_early_media_forking(LinphoneCall *call, SalMediaDescription *md){ + SalMediaDescription *cur_md=call->resultdesc; + int i; + SalStreamDescription *ref_stream,*new_stream; + ms_message("Early media response received from another branch, checking if media can be forked to this new destination."); + + for (i=0;in_active_streams;++i){ + ref_stream=&cur_md->streams[i]; + new_stream=&md->streams[i]; + if (ref_stream->type==new_stream->type && ref_stream->payloads && new_stream->payloads){ + PayloadType *refpt, *newpt; + refpt=(PayloadType*)ref_stream->payloads->data; + newpt=(PayloadType*)new_stream->payloads->data; + if (strcmp(refpt->mime_type,newpt->mime_type)==0 && refpt->clock_rate==newpt->clock_rate + && payload_type_get_number(refpt)==payload_type_get_number(newpt)){ + MediaStream *ms=NULL; + if (ref_stream->type==SalAudio){ + ms=(MediaStream*)call->audiostream; + }else if (ref_stream->type==SalVideo){ + ms=(MediaStream*)call->videostream; + } + if (ms){ + RtpSession *session=ms->sessions.rtp_session; + const char *rtp_addr=new_stream->rtp_addr[0]!='\0' ? new_stream->rtp_addr : md->addr; + const char *rtcp_addr=new_stream->rtcp_addr[0]!='\0' ? new_stream->rtcp_addr : md->addr; + rtp_session_add_aux_remote_addr_full(session,rtp_addr,new_stream->rtp_port,rtcp_addr,new_stream->rtcp_port); + } + } + } + } +} + static void call_ringing(SalOp *h){ LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(h)); LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(h); @@ -309,7 +371,7 @@ static void call_ringing(SalOp *h){ /*accept early media */ if (call->audiostream && audio_stream_started(call->audiostream)){ /*streams already started */ - ms_message("Early media already started."); + try_early_media_forking(call,md); return; } if (lc->vtable.show) lc->vtable.show(lc); diff --git a/mediastreamer2 b/mediastreamer2 index 916b6465a..e0ff32eeb 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 916b6465a2f46883cb2ad17ccc7b8fc210f1a942 +Subproject commit e0ff32eebb29671e855cb3cfe9c0ea46929419b4 diff --git a/oRTP b/oRTP index ae0fec375..a9ffd72d7 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit ae0fec37506f4cc5de7dfc8ca84321b7ae311bbe +Subproject commit a9ffd72d73d459e531fad094d18eb18f67870aba diff --git a/tester/call_tester.c b/tester/call_tester.c index 852bc4d68..a59313626 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -1200,7 +1200,8 @@ static void call_waiting_indication_with_param(bool_t enable_caller_privacy) { if (pauline_called_by_laure && enable_caller_privacy ) CU_ASSERT_EQUAL(linphone_call_params_get_privacy(linphone_call_get_current_params(pauline_called_by_laure)),LinphonePrivacyId); - + /*wait a bit for ACK to be sent*/ + wait_for_list(lcs,NULL,0,1000); linphone_core_terminate_all_calls(pauline->lc); CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallEnd,1,2000)); @@ -2162,6 +2163,86 @@ static void statistics_sent_at_call_termination() { } #ifdef VIDEO_ENABLED +/*this is call forking with early media managed at client side (not by flexisip server)*/ +static void multiple_early_media(void) { + LinphoneCoreManager* marie1 = linphone_core_manager_new("marie_early_rc"); + LinphoneCoreManager* marie2 = linphone_core_manager_new("marie_early_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new("pauline_tcp_rc"); + MSList *lcs=NULL; + LinphoneCallParams *params=linphone_core_create_default_call_parameters(pauline->lc); + LinphoneVideoPolicy pol; + LinphoneCall *marie1_call; + LinphoneCall *marie2_call; + LinphoneCall *pauline_call; + int dummy=0; + char ringbackpath[256]; + snprintf(ringbackpath,sizeof(ringbackpath), "%s/sounds/hello8000.wav" /*use hello because rinback is too short*/, liblinphone_tester_file_prefix); + + pol.automatically_accept=1; + pol.automatically_initiate=1; + + linphone_core_enable_video(pauline->lc,TRUE,TRUE); + + linphone_core_enable_video(marie1->lc,TRUE,TRUE); + linphone_core_set_video_policy(marie1->lc,&pol); + /*use playfile for marie1 to avoid locking on capture card*/ + linphone_core_use_files(marie1->lc,TRUE); + linphone_core_set_play_file(marie1->lc,ringbackpath); + + linphone_core_enable_video(marie2->lc,TRUE,TRUE); + linphone_core_set_video_policy(marie2->lc,&pol); + linphone_core_set_audio_port_range(marie2->lc,40200,40300); + linphone_core_set_video_port_range(marie2->lc,40400,40500); + /*use playfile for marie2 to avoid locking on capture card*/ + linphone_core_use_files(marie2->lc,TRUE); + linphone_core_set_play_file(marie2->lc,ringbackpath); + + + lcs=ms_list_append(lcs,marie1->lc); + lcs=ms_list_append(lcs,marie2->lc); + lcs=ms_list_append(lcs,pauline->lc); + + linphone_call_params_enable_early_media_sending(params,TRUE); + linphone_call_params_enable_video(params,TRUE); + + linphone_core_invite_address_with_params(pauline->lc,marie1->identity,params); + linphone_call_params_destroy(params); + + CU_ASSERT_TRUE(wait_for_list(lcs, &marie1->stat.number_of_LinphoneCallIncomingEarlyMedia,1,3000)); + CU_ASSERT_TRUE(wait_for_list(lcs, &marie2->stat.number_of_LinphoneCallIncomingEarlyMedia,1,3000)); + CU_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallOutgoingEarlyMedia,1,3000)); + + pauline_call=linphone_core_get_current_call(pauline->lc); + marie1_call=linphone_core_get_current_call(marie1->lc); + marie2_call=linphone_core_get_current_call(marie2->lc); + + /*wait a bit that streams are established*/ + wait_for_list(lcs,&dummy,1,6000); + CU_ASSERT_TRUE(linphone_call_get_audio_stats(pauline_call)->download_bandwidth>70); + CU_ASSERT_TRUE(linphone_call_get_audio_stats(marie1_call)->download_bandwidth>70); + CU_ASSERT_TRUE(linphone_call_get_audio_stats(marie2_call)->download_bandwidth>70); + + linphone_core_accept_call(marie1->lc,linphone_core_get_current_call(marie1->lc)); + CU_ASSERT_TRUE(wait_for_list(lcs,&marie1->stat.number_of_LinphoneCallStreamsRunning,1,3000)); + CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallStreamsRunning,1,3000)); + + /*marie2 should get her call terminated*/ + CU_ASSERT_TRUE(wait_for_list(lcs,&marie2->stat.number_of_LinphoneCallEnd,1,1000)); + + /*wait a bit that streams are established*/ + wait_for_list(lcs,&dummy,1,1000); + CU_ASSERT_TRUE(linphone_call_get_audio_stats(pauline_call)->download_bandwidth>71); + CU_ASSERT_TRUE(linphone_call_get_audio_stats(marie1_call)->download_bandwidth>71); + + 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,&marie1->stat.number_of_LinphoneCallEnd,1,1000)); + + ms_list_free(lcs); + linphone_core_manager_destroy(marie1); + linphone_core_manager_destroy(marie2); + linphone_core_manager_destroy(pauline); +} #endif test_t call_tests[] = { @@ -2196,6 +2277,7 @@ test_t call_tests[] = { { "Call with video added", call_with_video_added }, { "Call with video added (random ports)", call_with_video_added_random_ports }, { "Call with video declined",call_with_declined_video}, + { "Call with multiple early media", multiple_early_media }, #endif { "SRTP ice call", srtp_ice_call }, { "ZRTP ice call", zrtp_ice_call }, diff --git a/tester/rcfiles/marie_early_rc b/tester/rcfiles/marie_early_rc index 844959eae..079a81879 100644 --- a/tester/rcfiles/marie_early_rc +++ b/tester/rcfiles/marie_early_rc @@ -30,8 +30,8 @@ subscribe=0 [rtp] -audio_rtp_port=8070 -video_rtp_port=8072 +audio_rtp_port=18070 +video_rtp_port=19072 [video] display=0 From 31d767f9e393b48956d4c3283c7a219321bb97d3 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Wed, 4 Jun 2014 15:17:48 +0200 Subject: [PATCH 63/94] fix compilation error --- coreapi/bellesip_sal/sal_sdp.c | 2 +- coreapi/linphonecore.c | 2 +- coreapi/linphonecore.h | 2 +- coreapi/proxy.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/coreapi/bellesip_sal/sal_sdp.c b/coreapi/bellesip_sal/sal_sdp.c index 158460377..650235399 100644 --- a/coreapi/bellesip_sal/sal_sdp.c +++ b/coreapi/bellesip_sal/sal_sdp.c @@ -581,7 +581,7 @@ static void sdp_parse_rtcp_xr_parameters(const belle_sdp_attribute_t *attribute, } config->stat_summary_enabled = (belle_sdp_rtcp_xr_attribute_has_stat_summary(xr_attr) != 0); if (config->stat_summary_enabled) { - belle_sip_list_t *stat_summary_flag_it; + const belle_sip_list_t *stat_summary_flag_it; for (stat_summary_flag_it = belle_sdp_rtcp_xr_attribute_get_stat_summary_flags(xr_attr); stat_summary_flag_it != NULL; stat_summary_flag_it = stat_summary_flag_it->next ) { const char *flag = (const char *)stat_summary_flag_it->data; if (flag != NULL) { diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index f51453475..468481b42 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -2841,7 +2841,7 @@ LinphoneCall * linphone_core_invite_address_with_params(LinphoneCore *lc, const if (proxy!=NULL) { from=linphone_proxy_config_get_identity(proxy); - cp->avpf_enabled = linphone_proxy_config_is_avpf_enabled(proxy); + cp->avpf_enabled = linphone_proxy_config_avpf_enabled(proxy); cp->avpf_rr_interval = linphone_proxy_config_get_avpf_rr_interval(proxy); } diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index c2d565695..b7e297e0a 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -941,7 +941,7 @@ LINPHONE_PUBLIC void linphone_proxy_config_enable_avpf(LinphoneProxyConfig *cfg, * @param[in] cfg #LinphoneProxyConfig object * @return True if AVPF/SAVPF is enabled, false otherwise. */ -LINPHONE_PUBLIC bool_t linphone_proxy_config_is_avpf_enabled(LinphoneProxyConfig *cfg); +LINPHONE_PUBLIC bool_t linphone_proxy_config_avpf_enabled(LinphoneProxyConfig *cfg); /** * Set the interval between regular RTCP reports when using AVPF/SAVPF. diff --git a/coreapi/proxy.c b/coreapi/proxy.c index 25696691d..dde023bc4 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -1557,7 +1557,7 @@ void linphone_proxy_config_enable_avpf(LinphoneProxyConfig *cfg, bool_t enable) cfg->avpf_enabled = enable; } -bool_t linphone_proxy_config_is_avpf_enabled(LinphoneProxyConfig *cfg) { +bool_t linphone_proxy_config_avpf_enabled(LinphoneProxyConfig *cfg) { return cfg->avpf_enabled; } From 7c3924053be8b5a0b636224c62b37000b45c2543 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Wed, 4 Jun 2014 15:41:15 +0200 Subject: [PATCH 64/94] fix API naming --- java/common/org/linphone/core/LinphoneProxyConfig.java | 4 ++++ java/impl/org/linphone/core/LinphoneProxyConfigImpl.java | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/java/common/org/linphone/core/LinphoneProxyConfig.java b/java/common/org/linphone/core/LinphoneProxyConfig.java index b695e08f7..5c2b8f1e6 100644 --- a/java/common/org/linphone/core/LinphoneProxyConfig.java +++ b/java/common/org/linphone/core/LinphoneProxyConfig.java @@ -181,6 +181,10 @@ public interface LinphoneProxyConfig { */ int getAvpfRRInterval(); + /** + * Whether AVPF is used for calls through this proxy. + */ + void avpfEnabled(); /** * Set optional contact parameters that will be added to the contact information sent in the registration. diff --git a/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java b/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java index 53db2a526..aface8e36 100644 --- a/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java +++ b/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java @@ -203,6 +203,12 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { public void enableAvpf(boolean enable) { enableAvpf(nativePtr, enable); } + + private native boolean avpfEnabled(long nativePtr); + @Override + public boolean avpfEnabled() { + return avpfEnabled(nativePtr); + } private native void setAvpfRRInterval(long nativePtr, int interval); @Override From 22f6228c961a0621bff573e242cd5674a638cebe Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Wed, 4 Jun 2014 16:31:29 +0200 Subject: [PATCH 65/94] fix android build --- coreapi/linphonecore_jni.cc | 4 ++++ java/common/org/linphone/core/LinphoneProxyConfig.java | 3 ++- mediastreamer2 | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index e1f2da371..a24d2c589 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -2948,6 +2948,10 @@ JNIEXPORT void JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_enableAvpf linphone_proxy_config_enable_avpf((LinphoneProxyConfig *)ptr, (bool)enable); } +JNIEXPORT jboolean JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_avpfEnabled(JNIEnv *env, jobject thiz, jlong ptr) { + return linphone_proxy_config_avpf_enabled((LinphoneProxyConfig *)ptr); +} + JNIEXPORT void JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_setAvpfRRInterval(JNIEnv *env, jobject thiz, jlong ptr, jint interval) { linphone_proxy_config_set_avpf_rr_interval((LinphoneProxyConfig *)ptr, (uint8_t)interval); } diff --git a/java/common/org/linphone/core/LinphoneProxyConfig.java b/java/common/org/linphone/core/LinphoneProxyConfig.java index 5c2b8f1e6..ded4f9457 100644 --- a/java/common/org/linphone/core/LinphoneProxyConfig.java +++ b/java/common/org/linphone/core/LinphoneProxyConfig.java @@ -183,8 +183,9 @@ public interface LinphoneProxyConfig { /** * Whether AVPF is used for calls through this proxy. + * @return */ - void avpfEnabled(); + boolean avpfEnabled(); /** * Set optional contact parameters that will be added to the contact information sent in the registration. diff --git a/mediastreamer2 b/mediastreamer2 index e0ff32eeb..9c3d93635 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit e0ff32eebb29671e855cb3cfe9c0ea46929419b4 +Subproject commit 9c3d936356fe4d4c8a1fa8ca57a47598ece44f81 From 16c47f7b01ccda9517daa7ea0daf41687faa5d3c Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Wed, 4 Jun 2014 16:40:10 +0200 Subject: [PATCH 66/94] fix linphone_core_update_call() --- coreapi/linphonecore.c | 8 +++++++- tester/register_tester.c | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 468481b42..6c79d7c71 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -3148,7 +3148,13 @@ int linphone_core_update_call(LinphoneCore *lc, LinphoneCall *call, const Linpho bool_t has_video = FALSE; #endif - if (call->state!=LinphoneCallStreamsRunning){ + switch(call->state){ + case LinphoneCallIncomingEarlyMedia: + case LinphoneCallIncomingReceived: + case LinphoneCallStreamsRunning: + /*these states are allowed for linphone_core_update_call()*/ + 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/tester/register_tester.c b/tester/register_tester.c index 15584fc6f..2e7d330ff 100644 --- a/tester/register_tester.c +++ b/tester/register_tester.c @@ -681,7 +681,7 @@ static void io_recv_error_late_recovery(){ CU_ASSERT_TRUE(wait_for(lc,NULL,&counters->number_of_LinphoneRegistrationProgress,(register_ok-number_of_udp_proxy)+register_ok /*because 1 udp*/)); CU_ASSERT_EQUAL(counters->number_of_LinphoneRegistrationFailed,0) - CU_ASSERT_TRUE(wait_for_list(lcs=ms_list_append(NULL,lc),&counters->number_of_LinphoneRegistrationFailed,(register_ok-number_of_udp_proxy),sal_get_refresher_retry_after(lc->sal)+1000)); + CU_ASSERT_TRUE(wait_for_list(lcs=ms_list_append(NULL,lc),&counters->number_of_LinphoneRegistrationFailed,(register_ok-number_of_udp_proxy),sal_get_refresher_retry_after(lc->sal)+3000)); sal_set_recv_error(lc->sal, 1); /*reset*/ sal_set_send_error(lc->sal, 0); From e4f764eb5e4a1b87627f971f9426f63ed2fca1d6 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Wed, 4 Jun 2014 12:01:36 +0200 Subject: [PATCH 67/94] display error message when invalid argument is given to liblinphone_tester --- tester/liblinphone_tester.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tester/liblinphone_tester.c b/tester/liblinphone_tester.c index f47a556b9..7b5043680 100644 --- a/tester/liblinphone_tester.c +++ b/tester/liblinphone_tester.c @@ -203,6 +203,7 @@ int main (int argc, char *argv[]) } return 0; } else { + fprintf(stderr, "Unknown option \"%s\"\n", argv[i]); \ helper(argv[0]); return -1; } From e10667c4aed1b49e9e43abf6b490603331069472 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Wed, 4 Jun 2014 12:02:43 +0200 Subject: [PATCH 68/94] do not write empty sections in PUBLISH report for quality reporting --- coreapi/quality_reporting.c | 194 ++++++++++++++++++++---------------- coreapi/quality_reporting.h | 3 +- 2 files changed, 109 insertions(+), 88 deletions(-) diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c index 7da31f3d1..370ab97a7 100644 --- a/coreapi/quality_reporting.c +++ b/coreapi/quality_reporting.c @@ -30,13 +30,16 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /*************************************************************************** * TODO / REMINDER LIST - ****************************************************************************/ -/*For codecs that are able to change sample rates, the lowest and highest sample rates MUST be reported (e.g., 8000;16000). + *************************************************************************** +For codecs that are able to change sample rates, the lowest and highest sample rates MUST be reported (e.g., 8000;16000). moslq == moscq video: what happens if doing stop/resume? one time value: average? worst value? -rlq value: need algo to compute it*/ -/*************************************************************************** +rlq value: need algo to compute it + + - The Session report when session terminates, media change (codec change or a session fork), session terminates due to no media packets being received + - The Interval report SHOULD be used for periodic or interval reporting + *************************************************************************** * END OF TODO / REMINDER LIST ****************************************************************************/ @@ -73,13 +76,13 @@ static void append_to_buffer_valist(char **buff, size_t *buff_size, size_t *offs /*if we are out of memory, we add some size to buffer*/ if (ret == BELLE_SIP_BUFFER_OVERFLOW) { /*some compilers complain that size_t cannot be formatted as unsigned long, hence forcing cast*/ - ms_warning("Buffer was too small to contain the whole report - doubling its size from %lu to %lu", - (unsigned long)*buff_size, (unsigned long)2 * *buff_size); + ms_warning("Buffer was too small to contain the whole report - increasing its size from %lu to %lu", + (unsigned long)*buff_size, (unsigned long)*buff_size + 2048); *buff_size += 2048; *buff = (char *) ms_realloc(*buff, *buff_size); *offset = prevoffset; - /*recall myself since we did not write all things into the buffer but + /*recall itself since we did not write all things into the buffer but only a part of it*/ append_to_buffer_valist(buff, buff_size, offset, fmt, args); } @@ -98,38 +101,46 @@ static void append_to_buffer(char **buff, size_t *buff_size, size_t *offset, con #define APPEND_IF(buffer, size, offset, fmt, arg, cond) if (cond) append_to_buffer(buffer, size, offset, fmt, arg) #define IF_NUM_IN_RANGE(num, inf, sup, statement) if (inf <= num && num <= sup) statement -static bool_t are_metrics_filled(const reporting_content_metrics_t rm) { - IF_NUM_IN_RANGE(rm.packet_loss.network_packet_loss_rate, 0, 255, return TRUE); - IF_NUM_IN_RANGE(rm.packet_loss.jitter_buffer_discard_rate, 0, 255, return TRUE); - IF_NUM_IN_RANGE(rm.quality_estimates.moslq, 1, 5, return TRUE); - IF_NUM_IN_RANGE(rm.quality_estimates.moscq, 1, 5, return TRUE); +#define METRICS_PACKET_LOSS 1 << 0 +#define METRICS_QUALITY_ESTIMATES 1 << 1 +#define METRICS_SESSION_DESCRIPTION 1 << 2 +#define METRICS_JITTER_BUFFER 1 << 3 +#define METRICS_DELAY 1 << 4 +#define METRICS_SIGNAL 1 << 5 +static uint8_t are_metrics_filled(const reporting_content_metrics_t rm) { + uint8_t ret = 0; + + IF_NUM_IN_RANGE(rm.packet_loss.network_packet_loss_rate, 0, 255, ret&=METRICS_PACKET_LOSS); + IF_NUM_IN_RANGE(rm.packet_loss.jitter_buffer_discard_rate, 0, 255, ret&=METRICS_PACKET_LOSS); /*since these are same values than local ones, do not check them*/ - /*if (rm.session_description.payload_type != -1) return TRUE;*/ - /*if (rm.session_description.payload_desc != NULL) return TRUE;*/ - /*if (rm.session_description.sample_rate != -1) return TRUE;*/ - if (rm.session_description.frame_duration != -1) return TRUE; - /*if (rm.session_description.fmtp != NULL) return TRUE;*/ - if (rm.session_description.packet_loss_concealment != -1) return TRUE; + /*if (rm.session_description.payload_type != -1) ret&=METRICS_SESSION_DESCRIPTION;*/ + /*if (rm.session_description.payload_desc != NULL) ret&=METRICS_SESSION_DESCRIPTION;*/ + /*if (rm.session_description.sample_rate != -1) ret&=METRICS_SESSION_DESCRIPTION;*/ + /*if (rm.session_description.fmtp != NULL) ret&=METRICS_SESSION_DESCRIPTION;*/ + if (rm.session_description.frame_duration != -1) ret&=METRICS_SESSION_DESCRIPTION; + if (rm.session_description.packet_loss_concealment != -1) ret&=METRICS_SESSION_DESCRIPTION; - IF_NUM_IN_RANGE(rm.jitter_buffer.adaptive, 0, 3, return TRUE); - IF_NUM_IN_RANGE(rm.jitter_buffer.nominal, 0, 65535, return TRUE); - IF_NUM_IN_RANGE(rm.jitter_buffer.max, 0, 65535, return TRUE); - IF_NUM_IN_RANGE(rm.jitter_buffer.abs_max, 0, 65535, return TRUE); + IF_NUM_IN_RANGE(rm.jitter_buffer.adaptive, 0, 3, ret&=METRICS_JITTER_BUFFER); + IF_NUM_IN_RANGE(rm.jitter_buffer.nominal, 0, 65535, ret&=METRICS_JITTER_BUFFER); + IF_NUM_IN_RANGE(rm.jitter_buffer.max, 0, 65535, ret&=METRICS_JITTER_BUFFER); + IF_NUM_IN_RANGE(rm.jitter_buffer.abs_max, 0, 65535, ret&=METRICS_JITTER_BUFFER); - IF_NUM_IN_RANGE(rm.delay.round_trip_delay, 0, 65535, return TRUE); - IF_NUM_IN_RANGE(rm.delay.end_system_delay, 0, 65535, return TRUE); - IF_NUM_IN_RANGE(rm.delay.symm_one_way_delay, 0, 65535, return TRUE); - IF_NUM_IN_RANGE(rm.delay.interarrival_jitter, 0, 65535, return TRUE); - IF_NUM_IN_RANGE(rm.delay.mean_abs_jitter, 0, 65535, return TRUE); + IF_NUM_IN_RANGE(rm.delay.round_trip_delay, 0, 65535, ret&=METRICS_DELAY); + IF_NUM_IN_RANGE(rm.delay.end_system_delay, 0, 65535, ret&=METRICS_DELAY); + IF_NUM_IN_RANGE(rm.delay.symm_one_way_delay, 0, 65535, ret&=METRICS_DELAY); + IF_NUM_IN_RANGE(rm.delay.interarrival_jitter, 0, 65535, ret&=METRICS_DELAY); + IF_NUM_IN_RANGE(rm.delay.mean_abs_jitter, 0, 65535, ret&=METRICS_DELAY); - if (rm.signal.level != 127) return TRUE; - if (rm.signal.noise_level != 127) return TRUE; + if (rm.signal.level != 127) ret&=METRICS_SIGNAL; + if (rm.signal.noise_level != 127) ret&=METRICS_SIGNAL; - IF_NUM_IN_RANGE(rm.quality_estimates.rlq, 1, 120, return TRUE); - IF_NUM_IN_RANGE(rm.quality_estimates.rcq, 1, 120, return TRUE); + IF_NUM_IN_RANGE(rm.quality_estimates.moslq, 1, 5, ret&=METRICS_QUALITY_ESTIMATES); + IF_NUM_IN_RANGE(rm.quality_estimates.moscq, 1, 5, ret&=METRICS_QUALITY_ESTIMATES); + IF_NUM_IN_RANGE(rm.quality_estimates.rlq, 1, 120, ret&=METRICS_QUALITY_ESTIMATES); + IF_NUM_IN_RANGE(rm.quality_estimates.rcq, 1, 120, ret&=METRICS_QUALITY_ESTIMATES); - return FALSE; + return ret; } static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * offset, const reporting_content_metrics_t rm) { @@ -140,6 +151,7 @@ static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * off /*char * gap_loss_density_str = NULL;*/ char * moslq_str = NULL; char * moscq_str = NULL; + uint8_t available_metrics = are_metrics_filled(rm); if (rm.timestamps.start > 0) timestamps_start_str = linphone_timestamp_to_rfc3339_string(rm.timestamps.start); @@ -156,63 +168,74 @@ static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * off APPEND_IF_NOT_NULL_STR(buffer, size, offset, " START=%s", timestamps_start_str); APPEND_IF_NOT_NULL_STR(buffer, size, offset, " STOP=%s", timestamps_stop_str); - append_to_buffer(buffer, size, offset, "\r\nSessionDesc:"); - APPEND_IF(buffer, size, offset, " PT=%d", rm.session_description.payload_type, rm.session_description.payload_type != -1); - APPEND_IF_NOT_NULL_STR(buffer, size, offset, " PD=%s", rm.session_description.payload_desc); - APPEND_IF(buffer, size, offset, " SR=%d", rm.session_description.sample_rate, rm.session_description.sample_rate != -1); - APPEND_IF(buffer, size, offset, " FD=%d", rm.session_description.frame_duration, rm.session_description.frame_duration != -1); - /*append_to_buffer(buffer, size, offset, " FO=%d", rm.session_description.frame_ocets);*/ - /*append_to_buffer(buffer, size, offset, " FPP=%d", rm.session_description.frames_per_sec);*/ - /*append_to_buffer(buffer, size, offset, " PPS=%d", rm.session_description.packets_per_sec);*/ - APPEND_IF_NOT_NULL_STR(buffer, size, offset, " FMTP=\"%s\"", rm.session_description.fmtp); - APPEND_IF(buffer, size, offset, " PLC=%d", rm.session_description.packet_loss_concealment, rm.session_description.packet_loss_concealment != -1); - /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " SSUP=%s", rm.session_description.silence_suppression_state);*/ + if ((available_metrics & METRICS_SESSION_DESCRIPTION) != 0){ + append_to_buffer(buffer, size, offset, "\r\nSessionDesc:"); + APPEND_IF(buffer, size, offset, " PT=%d", rm.session_description.payload_type, rm.session_description.payload_type != -1); + APPEND_IF_NOT_NULL_STR(buffer, size, offset, " PD=%s", rm.session_description.payload_desc); + APPEND_IF(buffer, size, offset, " SR=%d", rm.session_description.sample_rate, rm.session_description.sample_rate != -1); + APPEND_IF(buffer, size, offset, " FD=%d", rm.session_description.frame_duration, rm.session_description.frame_duration != -1); + /*append_to_buffer(buffer, size, offset, " FO=%d", rm.session_description.frame_ocets);*/ + /*append_to_buffer(buffer, size, offset, " FPP=%d", rm.session_description.frames_per_sec);*/ + /*append_to_buffer(buffer, size, offset, " PPS=%d", rm.session_description.packets_per_sec);*/ + APPEND_IF_NOT_NULL_STR(buffer, size, offset, " FMTP=\"%s\"", rm.session_description.fmtp); + APPEND_IF(buffer, size, offset, " PLC=%d", rm.session_description.packet_loss_concealment, rm.session_description.packet_loss_concealment != -1); + /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " SSUP=%s", rm.session_description.silence_suppression_state);*/ + } - append_to_buffer(buffer, size, offset, "\r\nJitterBuffer:"); - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBA=%d", rm.jitter_buffer.adaptive, 0, 3); - /*APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBR=%d", rm.jitter_buffer.rate, 0, 15);*/ - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBN=%d", rm.jitter_buffer.nominal, 0, 65535); - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBM=%d", rm.jitter_buffer.max, 0, 65535); - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBX=%d", rm.jitter_buffer.abs_max, 0, 65535); + if ((available_metrics & METRICS_JITTER_BUFFER) != 0){ + append_to_buffer(buffer, size, offset, "\r\nJitterBuffer:"); + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBA=%d", rm.jitter_buffer.adaptive, 0, 3); + /*APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBR=%d", rm.jitter_buffer.rate, 0, 15);*/ + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBN=%d", rm.jitter_buffer.nominal, 0, 65535); + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBM=%d", rm.jitter_buffer.max, 0, 65535); + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBX=%d", rm.jitter_buffer.abs_max, 0, 65535); - append_to_buffer(buffer, size, offset, "\r\nPacketLoss:"); - APPEND_IF_NOT_NULL_STR(buffer, size, offset, " NLR=%s", network_packet_loss_rate_str); - APPEND_IF_NOT_NULL_STR(buffer, size, offset, " JDR=%s", jitter_buffer_discard_rate_str); + append_to_buffer(buffer, size, offset, "\r\nPacketLoss:"); + APPEND_IF_NOT_NULL_STR(buffer, size, offset, " NLR=%s", network_packet_loss_rate_str); + APPEND_IF_NOT_NULL_STR(buffer, size, offset, " JDR=%s", jitter_buffer_discard_rate_str); + } - /*append_to_buffer(buffer, size, offset, "\r\nBurstGapLoss:");*/ - /* append_to_buffer(buffer, size, offset, " BLD=%d", rm.burst_gap_loss.burst_loss_density);*/ - /* append_to_buffer(buffer, size, offset, " BD=%d", rm.burst_gap_loss.burst_duration);*/ - /* APPEND_IF_NOT_NULL_STR(buffer, size, offset, " GLD=%s", gap_loss_density_str);*/ - /* append_to_buffer(buffer, size, offset, " GD=%d", rm.burst_gap_loss.gap_duration);*/ - /* append_to_buffer(buffer, size, offset, " GMIN=%d", rm.burst_gap_loss.min_gap_threshold);*/ + /*append_to_buffer(buffer, size, offset, "\r\nBurstGapLoss:");*/ + /* append_to_buffer(buffer, size, offset, " BLD=%d", rm.burst_gap_loss.burst_loss_density);*/ + /* append_to_buffer(buffer, size, offset, " BD=%d", rm.burst_gap_loss.burst_duration);*/ + /* APPEND_IF_NOT_NULL_STR(buffer, size, offset, " GLD=%s", gap_loss_density_str);*/ + /* append_to_buffer(buffer, size, offset, " GD=%d", rm.burst_gap_loss.gap_duration);*/ + /* append_to_buffer(buffer, size, offset, " GMIN=%d", rm.burst_gap_loss.min_gap_threshold);*/ - append_to_buffer(buffer, size, offset, "\r\nDelay:"); - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RTD=%d", rm.delay.round_trip_delay, 0, 65535); - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " ESD=%d", rm.delay.end_system_delay, 0, 65535); - /*APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " OWD=%d", rm.delay.one_way_delay, 0, 65535);*/ - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " SOWD=%d", rm.delay.symm_one_way_delay, 0, 65535); - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " IAJ=%d", rm.delay.interarrival_jitter, 0, 65535); - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " MAJ=%d", rm.delay.mean_abs_jitter, 0, 65535); + if ((available_metrics & METRICS_DELAY) != 0){ + append_to_buffer(buffer, size, offset, "\r\nDelay:"); + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RTD=%d", rm.delay.round_trip_delay, 0, 65535); + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " ESD=%d", rm.delay.end_system_delay, 0, 65535); + /*APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " OWD=%d", rm.delay.one_way_delay, 0, 65535);*/ + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " SOWD=%d", rm.delay.symm_one_way_delay, 0, 65535); + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " IAJ=%d", rm.delay.interarrival_jitter, 0, 65535); + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " MAJ=%d", rm.delay.mean_abs_jitter, 0, 65535); + } - append_to_buffer(buffer, size, offset, "\r\nSignal:"); - APPEND_IF(buffer, size, offset, " SL=%d", rm.signal.level, rm.signal.level != 127); - APPEND_IF(buffer, size, offset, " NL=%d", rm.signal.noise_level, rm.signal.noise_level != 127); - /*append_to_buffer(buffer, size, offset, " RERL=%d", rm.signal.residual_echo_return_loss);*/ + if ((available_metrics & METRICS_SIGNAL) != 0){ + append_to_buffer(buffer, size, offset, "\r\nSignal:"); + APPEND_IF(buffer, size, offset, " SL=%d", rm.signal.level, rm.signal.level != 127); + APPEND_IF(buffer, size, offset, " NL=%d", rm.signal.noise_level, rm.signal.noise_level != 127); + /*append_to_buffer(buffer, size, offset, " RERL=%d", rm.signal.residual_echo_return_loss);*/ + } + + if ((available_metrics & METRICS_QUALITY_ESTIMATES) != 0){ + append_to_buffer(buffer, size, offset, "\r\nQualityEst:"); + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RLQ=%d", rm.quality_estimates.rlq, 1, 120); + /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " RLQEstAlg=%s", rm.quality_estimates.rlqestalg);*/ + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RCQ=%d", rm.quality_estimates.rcq, 1, 120); + /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " RCQEstAlgo=%s", rm.quality_estimates.rcqestalg);*/ + /*append_to_buffer(buffer, size, offset, " EXTRI=%d", rm.quality_estimates.extri);*/ + /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " ExtRIEstAlg=%s", rm.quality_estimates.extriestalg);*/ + /*append_to_buffer(buffer, size, offset, " EXTRO=%d", rm.quality_estimates.extro);*/ + /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " ExtROEstAlg=%s", rm.quality_estimates.extroutestalg);*/ + APPEND_IF_NOT_NULL_STR(buffer, size, offset, " MOSLQ=%s", moslq_str); + /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " MOSLQEstAlgo=%s", rm.quality_estimates.moslqestalg);*/ + APPEND_IF_NOT_NULL_STR(buffer, size, offset, " MOSCQ=%s", moscq_str); + /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " MOSCQEstAlgo=%s", rm.quality_estimates.moscqestalg);*/ + /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " QoEEstAlg=%s", rm.quality_estimates.qoestalg);*/ + } - append_to_buffer(buffer, size, offset, "\r\nQualityEst:"); - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RLQ=%d", rm.quality_estimates.rlq, 1, 120); - /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " RLQEstAlg=%s", rm.quality_estimates.rlqestalg);*/ - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RCQ=%d", rm.quality_estimates.rcq, 1, 120); - /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " RCQEstAlgo=%s", rm.quality_estimates.rcqestalg);*/ - /*append_to_buffer(buffer, size, offset, " EXTRI=%d", rm.quality_estimates.extri);*/ - /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " ExtRIEstAlg=%s", rm.quality_estimates.extriestalg);*/ - /*append_to_buffer(buffer, size, offset, " EXTRO=%d", rm.quality_estimates.extro);*/ - /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " ExtROEstAlg=%s", rm.quality_estimates.extroutestalg);*/ - APPEND_IF_NOT_NULL_STR(buffer, size, offset, " MOSLQ=%s", moslq_str); - /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " MOSLQEstAlgo=%s", rm.quality_estimates.moslqestalg);*/ - APPEND_IF_NOT_NULL_STR(buffer, size, offset, " MOSCQ=%s", moscq_str); - /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " MOSCQEstAlgo=%s", rm.quality_estimates.moscqestalg);*/ - /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " QoEEstAlg=%s", rm.quality_estimates.qoestalg);*/ append_to_buffer(buffer, size, offset, "\r\n"); ms_free(timestamps_start_str); @@ -261,7 +284,7 @@ static void reporting_publish(const LinphoneCall* call, const reporting_session_ append_to_buffer(&buffer, &size, &offset, "LocalMetrics:\r\n"); append_metrics_to_buffer(&buffer, &size, &offset, report->local_metrics); - if (are_metrics_filled(report->remote_metrics)) { + if (are_metrics_filled(report->remote_metrics)!=0) { append_to_buffer(&buffer, &size, &offset, "RemoteMetrics:\r\n"); append_metrics_to_buffer(&buffer, &size, &offset, report->remote_metrics); } @@ -329,9 +352,6 @@ static bool_t reporting_enabled(const LinphoneCall * call) { } void linphone_reporting_update_ip(LinphoneCall * call) { - /*This function can be called in two different cases: - - 1) at start when call is starting, remote ip/port info might be the proxy ones to which callee is registered - - 2) later, if we found a direct route between caller and callee with ICE/Stun, ip/port are updated for the direct route access*/ if (! reporting_enabled(call)) return; diff --git a/coreapi/quality_reporting.h b/coreapi/quality_reporting.h index c182a4210..651daeace 100644 --- a/coreapi/quality_reporting.h +++ b/coreapi/quality_reporting.h @@ -135,8 +135,9 @@ void linphone_reporting_update(LinphoneCall * call, int stats_type); /** * Fill IP information about a given call. This function must be called each - * time state is 'LinphoneCallStreamsRunning' since IP might be updated (if we + * time call state is 'LinphoneCallStreamsRunning' since IP might be updated (if we * found a direct route between caller and callee for example). + * When call is starting, remote IP/port might be the proxy ones to which callee is registered * @param call #LinphoneCall object to consider * */ From 1946ccaaef1518af985f3505a8c4d3c94ea3e368 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Wed, 4 Jun 2014 16:40:26 +0200 Subject: [PATCH 69/94] Quality reporting: compute average values for instaneous parameters and added custom extension for qos analyzer to get its input/output on each RTCP packet received --- coreapi/linphonecall.c | 18 ++-- coreapi/quality_reporting.c | 199 +++++++++++++++++++++++------------- coreapi/quality_reporting.h | 26 +++-- mediastreamer2 | 2 +- 4 files changed, 156 insertions(+), 89 deletions(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index d80a76920..c6087580c 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -871,7 +871,7 @@ void linphone_call_set_state(LinphoneCall *call, LinphoneCallState cstate, const if (cstate==LinphoneCallEnd){ if (call->log->status == LinphoneCallSuccess) - linphone_reporting_publish(call); + linphone_reporting_publish_on_call_term(call); } if (cstate==LinphoneCallReleased){ @@ -1778,7 +1778,7 @@ static int get_ideal_audio_bw(LinphoneCall *call, const SalMediaDescription *md, const LinphoneCallParams *params=&call->params; bool_t will_use_video=linphone_core_media_description_contains_video_stream(md); bool_t forced=FALSE; - + if (desc->bandwidth>0) remote_bw=desc->bandwidth; else if (md->bandwidth>0) { /*case where b=AS is given globally, not per stream*/ @@ -1790,7 +1790,7 @@ static int get_ideal_audio_bw(LinphoneCall *call, const SalMediaDescription *md, }else upload_bw=total_upload_bw; upload_bw=get_min_bandwidth(upload_bw,remote_bw); if (!will_use_video || forced) return upload_bw; - + if (bandwidth_is_greater(upload_bw,512)){ upload_bw=100; }else if (bandwidth_is_greater(upload_bw,256)){ @@ -1823,13 +1823,13 @@ static RtpProfile *make_profile(LinphoneCall *call, const SalMediaDescription *m LinphoneCore *lc=call->core; int up_ptime=0; const LinphoneCallParams *params=&call->params; - + *used_pt=-1; if (desc->type==SalAudio) bw=get_ideal_audio_bw(call,md,desc); else if (desc->type==SalVideo) bw=get_video_bw(call,md,desc); - + for(elem=desc->payloads;elem!=NULL;elem=elem->next){ PayloadType *pt=(PayloadType*)elem->data; int number; @@ -2293,7 +2293,7 @@ static void linphone_call_log_fill_stats(LinphoneCallLog *log, MediaStream *st){ void linphone_call_stop_audio_stream(LinphoneCall *call) { if (call->audiostream!=NULL) { - linphone_reporting_update(call, LINPHONE_CALL_STATS_AUDIO); + linphone_reporting_update_media_info(call, LINPHONE_CALL_STATS_AUDIO); media_stream_reclaim_sessions(&call->audiostream->ms,&call->sessions[0]); rtp_session_unregister_event_queue(call->audiostream->ms.sessions.rtp_session,call->audiostream_app_evq); ortp_ev_queue_flush(call->audiostream_app_evq); @@ -2322,7 +2322,7 @@ void linphone_call_stop_audio_stream(LinphoneCall *call) { void linphone_call_stop_video_stream(LinphoneCall *call) { #ifdef VIDEO_ENABLED if (call->videostream!=NULL){ - linphone_reporting_update(call, LINPHONE_CALL_STATS_VIDEO); + linphone_reporting_update_media_info(call, LINPHONE_CALL_STATS_VIDEO); media_stream_reclaim_sessions(&call->videostream->ms,&call->sessions[1]); rtp_session_unregister_event_queue(call->videostream->ms.sessions.rtp_session,call->videostream_app_evq); ortp_ev_queue_flush(call->videostream_app_evq); @@ -2712,7 +2712,7 @@ static void linphone_core_disconnected(LinphoneCore *lc, LinphoneCall *call){ 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 : ""); if (from) ms_free(from); - + ms_message("On call [%p]: %s",call,temp); if (lc->vtable.display_warning!=NULL) lc->vtable.display_warning(lc,temp); @@ -2836,7 +2836,7 @@ void linphone_call_notify_stats_updated(LinphoneCall *call, int stream_index){ LinphoneCallStats *stats=&call->stats[stream_index]; LinphoneCore *lc=call->core; if (stats->updated){ - linphone_reporting_call_stats_updated(call, stream_index); + linphone_reporting_on_rtcp_received(call, stream_index); if (lc->vtable.call_stats_updated) lc->vtable.call_stats_updated(lc, call, stats); stats->updated = 0; diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c index 370ab97a7..50c283cf0 100644 --- a/coreapi/quality_reporting.c +++ b/coreapi/quality_reporting.c @@ -34,11 +34,14 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. For codecs that are able to change sample rates, the lowest and highest sample rates MUST be reported (e.g., 8000;16000). moslq == moscq video: what happens if doing stop/resume? -one time value: average? worst value? rlq value: need algo to compute it - The Session report when session terminates, media change (codec change or a session fork), session terminates due to no media packets being received - The Interval report SHOULD be used for periodic or interval reporting + + -> avg values + -> interval report + -> custom metrics *************************************************************************** * END OF TODO / REMINDER LIST ****************************************************************************/ @@ -95,6 +98,19 @@ static void append_to_buffer(char **buff, size_t *buff_size, size_t *offset, con va_end(args); } +static void reset_avg_metrics(reporting_session_report_t * rm){ + int i; + reporting_content_metrics_t * metrics[2] = {&rm->local_metrics, &rm->remote_metrics}; + + for (i = 0; i < 2; i++) { + metrics[i]->rtcp_xr_count = 0; + metrics[i]->jitter_buffer.nominal = 0; + metrics[i]->jitter_buffer.max = 0; + + metrics[i]->delay.round_trip_delay = 0; + metrics[i]->delay.symm_one_way_delay = 0; + } +} #define APPEND_IF_NOT_NULL_STR(buffer, size, offset, fmt, arg) if (arg != NULL) append_to_buffer(buffer, size, offset, fmt, arg) #define APPEND_IF_NUM_IN_RANGE(buffer, size, offset, fmt, arg, inf, sup) if (inf <= arg && arg <= sup) append_to_buffer(buffer, size, offset, fmt, arg) @@ -107,38 +123,45 @@ static void append_to_buffer(char **buff, size_t *buff_size, size_t *offset, con #define METRICS_JITTER_BUFFER 1 << 3 #define METRICS_DELAY 1 << 4 #define METRICS_SIGNAL 1 << 5 +#define METRICS_ADAPTIVE_ALGORITHM 1 << 6 + static uint8_t are_metrics_filled(const reporting_content_metrics_t rm) { uint8_t ret = 0; - IF_NUM_IN_RANGE(rm.packet_loss.network_packet_loss_rate, 0, 255, ret&=METRICS_PACKET_LOSS); - IF_NUM_IN_RANGE(rm.packet_loss.jitter_buffer_discard_rate, 0, 255, ret&=METRICS_PACKET_LOSS); + IF_NUM_IN_RANGE(rm.packet_loss.network_packet_loss_rate, 0, 255, ret|=METRICS_PACKET_LOSS); + IF_NUM_IN_RANGE(rm.packet_loss.jitter_buffer_discard_rate, 0, 255, ret|=METRICS_PACKET_LOSS); /*since these are same values than local ones, do not check them*/ - /*if (rm.session_description.payload_type != -1) ret&=METRICS_SESSION_DESCRIPTION;*/ - /*if (rm.session_description.payload_desc != NULL) ret&=METRICS_SESSION_DESCRIPTION;*/ - /*if (rm.session_description.sample_rate != -1) ret&=METRICS_SESSION_DESCRIPTION;*/ - /*if (rm.session_description.fmtp != NULL) ret&=METRICS_SESSION_DESCRIPTION;*/ - if (rm.session_description.frame_duration != -1) ret&=METRICS_SESSION_DESCRIPTION; - if (rm.session_description.packet_loss_concealment != -1) ret&=METRICS_SESSION_DESCRIPTION; + /*if (rm.session_description.payload_type != -1) ret|=METRICS_SESSION_DESCRIPTION;*/ + /*if (rm.session_description.payload_desc != NULL) ret|=METRICS_SESSION_DESCRIPTION;*/ + /*if (rm.session_description.sample_rate != -1) ret|=METRICS_SESSION_DESCRIPTION;*/ + /*if (rm.session_description.fmtp != NULL) ret|=METRICS_SESSION_DESCRIPTION;*/ + if (rm.session_description.frame_duration != -1) ret|=METRICS_SESSION_DESCRIPTION; + if (rm.session_description.packet_loss_concealment != -1) ret|=METRICS_SESSION_DESCRIPTION; - IF_NUM_IN_RANGE(rm.jitter_buffer.adaptive, 0, 3, ret&=METRICS_JITTER_BUFFER); - IF_NUM_IN_RANGE(rm.jitter_buffer.nominal, 0, 65535, ret&=METRICS_JITTER_BUFFER); - IF_NUM_IN_RANGE(rm.jitter_buffer.max, 0, 65535, ret&=METRICS_JITTER_BUFFER); - IF_NUM_IN_RANGE(rm.jitter_buffer.abs_max, 0, 65535, ret&=METRICS_JITTER_BUFFER); + IF_NUM_IN_RANGE(rm.jitter_buffer.adaptive, 0, 3, ret|=METRICS_JITTER_BUFFER); + IF_NUM_IN_RANGE(rm.jitter_buffer.abs_max, 0, 65535, ret|=METRICS_JITTER_BUFFER); - IF_NUM_IN_RANGE(rm.delay.round_trip_delay, 0, 65535, ret&=METRICS_DELAY); - IF_NUM_IN_RANGE(rm.delay.end_system_delay, 0, 65535, ret&=METRICS_DELAY); - IF_NUM_IN_RANGE(rm.delay.symm_one_way_delay, 0, 65535, ret&=METRICS_DELAY); - IF_NUM_IN_RANGE(rm.delay.interarrival_jitter, 0, 65535, ret&=METRICS_DELAY); - IF_NUM_IN_RANGE(rm.delay.mean_abs_jitter, 0, 65535, ret&=METRICS_DELAY); + IF_NUM_IN_RANGE(rm.delay.end_system_delay, 0, 65535, ret|=METRICS_DELAY); + IF_NUM_IN_RANGE(rm.delay.symm_one_way_delay, 0, 65535, ret|=METRICS_DELAY); + IF_NUM_IN_RANGE(rm.delay.interarrival_jitter, 0, 65535, ret|=METRICS_DELAY); + IF_NUM_IN_RANGE(rm.delay.mean_abs_jitter, 0, 65535, ret|=METRICS_DELAY); - if (rm.signal.level != 127) ret&=METRICS_SIGNAL; - if (rm.signal.noise_level != 127) ret&=METRICS_SIGNAL; + if (rm.signal.level != 127) ret|=METRICS_SIGNAL; + if (rm.signal.noise_level != 127) ret|=METRICS_SIGNAL; - IF_NUM_IN_RANGE(rm.quality_estimates.moslq, 1, 5, ret&=METRICS_QUALITY_ESTIMATES); - IF_NUM_IN_RANGE(rm.quality_estimates.moscq, 1, 5, ret&=METRICS_QUALITY_ESTIMATES); - IF_NUM_IN_RANGE(rm.quality_estimates.rlq, 1, 120, ret&=METRICS_QUALITY_ESTIMATES); - IF_NUM_IN_RANGE(rm.quality_estimates.rcq, 1, 120, ret&=METRICS_QUALITY_ESTIMATES); + if (rm.adaptive_algorithm.input!=NULL) ret|=METRICS_ADAPTIVE_ALGORITHM; + if (rm.adaptive_algorithm.output!=NULL) ret|=METRICS_ADAPTIVE_ALGORITHM; + + if (rm.rtcp_xr_count>0){ + IF_NUM_IN_RANGE(rm.jitter_buffer.nominal/rm.rtcp_xr_count, 0, 65535, ret|=METRICS_JITTER_BUFFER); + IF_NUM_IN_RANGE(rm.jitter_buffer.max/rm.rtcp_xr_count, 0, 65535, ret|=METRICS_JITTER_BUFFER); + IF_NUM_IN_RANGE(rm.delay.round_trip_delay, 0, 65535, ret|=METRICS_DELAY); + IF_NUM_IN_RANGE(rm.quality_estimates.moslq/rm.rtcp_xr_count, 1, 5, ret|=METRICS_QUALITY_ESTIMATES); + IF_NUM_IN_RANGE(rm.quality_estimates.moscq/rm.rtcp_xr_count, 1, 5, ret|=METRICS_QUALITY_ESTIMATES); + IF_NUM_IN_RANGE(rm.quality_estimates.rlq/rm.rtcp_xr_count, 1, 120, ret|=METRICS_QUALITY_ESTIMATES); + IF_NUM_IN_RANGE(rm.quality_estimates.rcq/rm.rtcp_xr_count, 1, 120, ret|=METRICS_QUALITY_ESTIMATES); + } return ret; } @@ -158,12 +181,6 @@ static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * off if (rm.timestamps.stop > 0) timestamps_stop_str = linphone_timestamp_to_rfc3339_string(rm.timestamps.stop); - IF_NUM_IN_RANGE(rm.packet_loss.network_packet_loss_rate, 0, 255, network_packet_loss_rate_str = float_to_one_decimal_string(rm.packet_loss.network_packet_loss_rate / 256)); - IF_NUM_IN_RANGE(rm.packet_loss.jitter_buffer_discard_rate, 0, 255, jitter_buffer_discard_rate_str = float_to_one_decimal_string(rm.packet_loss.jitter_buffer_discard_rate / 256)); - /*IF_NUM_IN_RANGE(rm.burst_gap_loss.gap_loss_density, 0, 10, gap_loss_density_str = float_to_one_decimal_string(rm.burst_gap_loss.gap_loss_density));*/ - IF_NUM_IN_RANGE(rm.quality_estimates.moslq, 1, 5, moslq_str = float_to_one_decimal_string(rm.quality_estimates.moslq)); - IF_NUM_IN_RANGE(rm.quality_estimates.moscq, 1, 5, moscq_str = float_to_one_decimal_string(rm.quality_estimates.moscq)); - append_to_buffer(buffer, size, offset, "Timestamps:"); APPEND_IF_NOT_NULL_STR(buffer, size, offset, " START=%s", timestamps_start_str); APPEND_IF_NOT_NULL_STR(buffer, size, offset, " STOP=%s", timestamps_stop_str); @@ -186,16 +203,22 @@ static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * off append_to_buffer(buffer, size, offset, "\r\nJitterBuffer:"); APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBA=%d", rm.jitter_buffer.adaptive, 0, 3); /*APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBR=%d", rm.jitter_buffer.rate, 0, 15);*/ - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBN=%d", rm.jitter_buffer.nominal, 0, 65535); - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBM=%d", rm.jitter_buffer.max, 0, 65535); + if (rm.rtcp_xr_count){ + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBN=%d", rm.jitter_buffer.nominal/rm.rtcp_xr_count, 0, 65535); + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBM=%d", rm.jitter_buffer.max/rm.rtcp_xr_count, 0, 65535); + } APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBX=%d", rm.jitter_buffer.abs_max, 0, 65535); append_to_buffer(buffer, size, offset, "\r\nPacketLoss:"); + IF_NUM_IN_RANGE(rm.packet_loss.network_packet_loss_rate, 0, 255, network_packet_loss_rate_str = float_to_one_decimal_string(rm.packet_loss.network_packet_loss_rate / 256)); + IF_NUM_IN_RANGE(rm.packet_loss.jitter_buffer_discard_rate, 0, 255, jitter_buffer_discard_rate_str = float_to_one_decimal_string(rm.packet_loss.jitter_buffer_discard_rate / 256)); + APPEND_IF_NOT_NULL_STR(buffer, size, offset, " NLR=%s", network_packet_loss_rate_str); APPEND_IF_NOT_NULL_STR(buffer, size, offset, " JDR=%s", jitter_buffer_discard_rate_str); } /*append_to_buffer(buffer, size, offset, "\r\nBurstGapLoss:");*/ + /*IF_NUM_IN_RANGE(rm.burst_gap_loss.gap_loss_density, 0, 10, gap_loss_density_str = float_to_one_decimal_string(rm.burst_gap_loss.gap_loss_density));*/ /* append_to_buffer(buffer, size, offset, " BLD=%d", rm.burst_gap_loss.burst_loss_density);*/ /* append_to_buffer(buffer, size, offset, " BD=%d", rm.burst_gap_loss.burst_duration);*/ /* APPEND_IF_NOT_NULL_STR(buffer, size, offset, " GLD=%s", gap_loss_density_str);*/ @@ -204,7 +227,9 @@ static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * off if ((available_metrics & METRICS_DELAY) != 0){ append_to_buffer(buffer, size, offset, "\r\nDelay:"); - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RTD=%d", rm.delay.round_trip_delay, 0, 65535); + if (rm.rtcp_xr_count){ + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RTD=%d", rm.delay.round_trip_delay/rm.rtcp_xr_count, 0, 65535); + } APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " ESD=%d", rm.delay.end_system_delay, 0, 65535); /*APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " OWD=%d", rm.delay.one_way_delay, 0, 65535);*/ APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " SOWD=%d", rm.delay.symm_one_way_delay, 0, 65535); @@ -219,11 +244,15 @@ static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * off /*append_to_buffer(buffer, size, offset, " RERL=%d", rm.signal.residual_echo_return_loss);*/ } + /*if quality estimates metrics are available, rtcp_xr_count should be always not null*/ if ((available_metrics & METRICS_QUALITY_ESTIMATES) != 0){ + IF_NUM_IN_RANGE(rm.quality_estimates.moslq/rm.rtcp_xr_count, 1, 5, moslq_str = float_to_one_decimal_string(rm.quality_estimates.moslq)); + IF_NUM_IN_RANGE(rm.quality_estimates.moscq/rm.rtcp_xr_count, 1, 5, moscq_str = float_to_one_decimal_string(rm.quality_estimates.moscq)); + append_to_buffer(buffer, size, offset, "\r\nQualityEst:"); - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RLQ=%d", rm.quality_estimates.rlq, 1, 120); + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RLQ=%d", rm.quality_estimates.rlq/rm.rtcp_xr_count, 1, 120); /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " RLQEstAlg=%s", rm.quality_estimates.rlqestalg);*/ - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RCQ=%d", rm.quality_estimates.rcq, 1, 120); + APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RCQ=%d", rm.quality_estimates.rcq/rm.rtcp_xr_count, 1, 120); /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " RCQEstAlgo=%s", rm.quality_estimates.rcqestalg);*/ /*append_to_buffer(buffer, size, offset, " EXTRI=%d", rm.quality_estimates.extri);*/ /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " ExtRIEstAlg=%s", rm.quality_estimates.extriestalg);*/ @@ -236,6 +265,12 @@ static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * off /*APPEND_IF_NOT_NULL_STR(buffer, size, offset, " QoEEstAlg=%s", rm.quality_estimates.qoestalg);*/ } + if ((available_metrics & METRICS_ADAPTIVE_ALGORITHM) != 0){ + append_to_buffer(buffer, size, offset, "\r\nAdaptiveAlg:"); + APPEND_IF_NOT_NULL_STR(buffer, size, offset, " IN=%s", rm.adaptive_algorithm.input); + APPEND_IF_NOT_NULL_STR(buffer, size, offset, " OUT=%s", rm.adaptive_algorithm.output); + } + append_to_buffer(buffer, size, offset, "\r\n"); ms_free(timestamps_start_str); @@ -247,7 +282,7 @@ static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * off ms_free(moscq_str); } -static void reporting_publish(const LinphoneCall* call, const reporting_session_report_t * report) { +static void send_report(const LinphoneCall* call, reporting_session_report_t * report) { LinphoneContent content = {0}; LinphoneAddress *addr; int expires = -1; @@ -264,6 +299,12 @@ static void reporting_publish(const LinphoneCall* call, const reporting_session_ return; } + addr = linphone_address_new(call->dest_proxy->statistics_collector); + if (addr == NULL) { + ms_warning("Asked to submit reporting statistics but no collector address found"); + return; + } + buffer = (char *) ms_malloc(size); content.type = ms_strdup("application"); content.subtype = ms_strdup("vq-rtcpxr"); @@ -291,17 +332,12 @@ static void reporting_publish(const LinphoneCall* call, const reporting_session_ APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, "DialogID: %s\r\n", report->dialog_id); content.data = buffer; - content.size = strlen((char*)content.data); + content.size = strlen(buffer); + linphone_core_publish(call->core, addr, "vq-rtcpxr", expires, &content); + linphone_address_destroy(addr); - addr = linphone_address_new(call->dest_proxy->statistics_collector); - if (addr != NULL) { - linphone_core_publish(call->core, addr, "vq-rtcpxr", expires, &content); - linphone_address_destroy(addr); - } else { - ms_warning("Asked to submit reporting statistics but no collector address found"); - } - + reset_avg_metrics(report); linphone_content_uninit(&content); } @@ -321,7 +357,7 @@ static const SalStreamDescription * get_media_stream_for_desc(const SalMediaDesc return NULL; } -static void reporting_update_ip(LinphoneCall * call, int stats_type) { +static void update_ip(LinphoneCall * call, int stats_type) { SalStreamType sal_stream_type = (stats_type == LINPHONE_CALL_STATS_AUDIO) ? SalAudio : SalVideo; if (call->log->reports[stats_type] != NULL) { const SalStreamDescription * local_desc = get_media_stream_for_desc(call->localdesc, sal_stream_type); @@ -347,29 +383,39 @@ static void reporting_update_ip(LinphoneCall * call, int stats_type) { } } -static bool_t reporting_enabled(const LinphoneCall * call) { +static bool_t is_reporting_enabled(const LinphoneCall * call) { return (call->dest_proxy != NULL && linphone_proxy_config_send_statistics_enabled(call->dest_proxy)); } +static void qos_analyser_on_action_suggested(void *user_data, const char * input, const char * output){ + reporting_content_metrics_t *metrics = (reporting_content_metrics_t*) user_data; + char * newstr = NULL; + newstr = ms_strdup_printf("%s%s;", metrics->adaptive_algorithm.input?metrics->adaptive_algorithm.input:"", input); + STR_REASSIGN(metrics->adaptive_algorithm.input, newstr) + + newstr = ms_strdup_printf("%s%s;", metrics->adaptive_algorithm.output?metrics->adaptive_algorithm.output:"", output); + STR_REASSIGN(metrics->adaptive_algorithm.output, newstr) +} + void linphone_reporting_update_ip(LinphoneCall * call) { - if (! reporting_enabled(call)) + if (! is_reporting_enabled(call)) return; - reporting_update_ip(call, LINPHONE_CALL_STATS_AUDIO); + update_ip(call, LINPHONE_CALL_STATS_AUDIO); if (linphone_call_params_video_enabled(linphone_call_get_current_params(call))) { - reporting_update_ip(call, LINPHONE_CALL_STATS_VIDEO); + update_ip(call, LINPHONE_CALL_STATS_VIDEO); } } -void linphone_reporting_update(LinphoneCall * call, int stats_type) { +void linphone_reporting_update_media_info(LinphoneCall * call, int stats_type) { reporting_session_report_t * report = call->log->reports[stats_type]; MediaStream * stream = NULL; const PayloadType * local_payload = NULL; const PayloadType * remote_payload = NULL; const LinphoneCallParams * current_params = linphone_call_get_current_params(call); - if (! reporting_enabled(call)) + if (! is_reporting_enabled(call)) return; STR_REASSIGN(report->info.call_id, ms_strdup(call->log->call_id)); @@ -430,14 +476,14 @@ void linphone_reporting_update(LinphoneCall * call, int stats_type) { } } -void linphone_reporting_call_stats_updated(LinphoneCall *call, int stats_type) { +void linphone_reporting_on_rtcp_received(LinphoneCall *call, int stats_type) { reporting_session_report_t * report = call->log->reports[stats_type]; reporting_content_metrics_t * metrics = NULL; LinphoneCallStats stats = call->stats[stats_type]; mblk_t *block = NULL; - if (! reporting_enabled(call)) + if (! is_reporting_enabled(call)) return; if (stats.updated == LINPHONE_CALL_STATS_RECEIVED_RTCP_UPDATE) { @@ -451,24 +497,33 @@ void linphone_reporting_call_stats_updated(LinphoneCall *call, int stats_type) { block = stats.sent_rtcp; } } + + ms_qos_analyser_set_on_action_suggested( + ms_bitrate_controller_get_qos_analyser(call->audiostream->ms.rc), + qos_analyser_on_action_suggested, + &report->local_metrics); + if (block != NULL) { switch (rtcp_XR_get_block_type(block)) { case RTCP_XR_VOIP_METRICS: { - uint8_t config; + uint8_t config = rtcp_XR_voip_metrics_get_rx_config(block); - metrics->quality_estimates.rcq = rtcp_XR_voip_metrics_get_r_factor(block); - metrics->quality_estimates.moslq = rtcp_XR_voip_metrics_get_mos_lq(block) / 10.f; - metrics->quality_estimates.moscq = rtcp_XR_voip_metrics_get_mos_cq(block) / 10.f; + metrics->rtcp_xr_count++; - metrics->jitter_buffer.nominal = rtcp_XR_voip_metrics_get_jb_nominal(block); - metrics->jitter_buffer.max = rtcp_XR_voip_metrics_get_jb_maximum(block); + metrics->quality_estimates.rcq += rtcp_XR_voip_metrics_get_r_factor(block); + metrics->quality_estimates.moslq += rtcp_XR_voip_metrics_get_mos_lq(block) / 10.f; + metrics->quality_estimates.moscq += rtcp_XR_voip_metrics_get_mos_cq(block) / 10.f; + + metrics->jitter_buffer.nominal += rtcp_XR_voip_metrics_get_jb_nominal(block); + metrics->jitter_buffer.max += rtcp_XR_voip_metrics_get_jb_maximum(block); metrics->jitter_buffer.abs_max = rtcp_XR_voip_metrics_get_jb_abs_max(block); + metrics->jitter_buffer.adaptive = (config >> 4) & 0x3; metrics->packet_loss.network_packet_loss_rate = rtcp_XR_voip_metrics_get_loss_rate(block); metrics->packet_loss.jitter_buffer_discard_rate = rtcp_XR_voip_metrics_get_discard_rate(block); - config = rtcp_XR_voip_metrics_get_rx_config(block); metrics->session_description.packet_loss_concealment = (config >> 6) & 0x3; - metrics->jitter_buffer.adaptive = (config >> 4) & 0x3; + + metrics->delay.round_trip_delay += rtcp_XR_voip_metrics_get_round_trip_delay(block); break; } default: { break; @@ -477,26 +532,28 @@ void linphone_reporting_call_stats_updated(LinphoneCall *call, int stats_type) { } } -void linphone_reporting_publish(LinphoneCall* call) { - if (! reporting_enabled(call)) +void linphone_reporting_publish_on_call_term(LinphoneCall* call) { + if (! is_reporting_enabled(call)) return; if (call->log->reports[LINPHONE_CALL_STATS_AUDIO] != NULL) { - reporting_publish(call, call->log->reports[LINPHONE_CALL_STATS_AUDIO]); + send_report(call, call->log->reports[LINPHONE_CALL_STATS_AUDIO]); } if (call->log->reports[LINPHONE_CALL_STATS_VIDEO] != NULL && linphone_call_params_video_enabled(linphone_call_get_current_params(call))) { - reporting_publish(call, call->log->reports[LINPHONE_CALL_STATS_VIDEO]); + send_report(call, call->log->reports[LINPHONE_CALL_STATS_VIDEO]); } } reporting_session_report_t * linphone_reporting_new() { int i; reporting_session_report_t * rm = ms_new0(reporting_session_report_t,1); - reporting_content_metrics_t * metrics[2] = {&rm->local_metrics, &rm->remote_metrics}; + + memset(rm, 0, sizeof(reporting_session_report_t)); + for (i = 0; i < 2; i++) { metrics[i]->session_description.payload_type = -1; metrics[i]->session_description.sample_rate = -1; @@ -509,20 +566,18 @@ reporting_session_report_t * linphone_reporting_new() { metrics[i]->jitter_buffer.adaptive = -1; /*metrics[i]->jitter_buffer.rate = -1;*/ - metrics[i]->jitter_buffer.nominal = -1; - metrics[i]->jitter_buffer.max = -1; metrics[i]->jitter_buffer.abs_max = -1; - metrics[i]->delay.round_trip_delay = -1; metrics[i]->delay.end_system_delay = -1; /*metrics[i]->delay.one_way_delay = -1;*/ - metrics[i]->delay.symm_one_way_delay = -1; metrics[i]->delay.interarrival_jitter = -1; metrics[i]->delay.mean_abs_jitter = -1; metrics[i]->signal.level = 127; metrics[i]->signal.noise_level = 127; } + + reset_avg_metrics(rm); return rm; } @@ -542,6 +597,8 @@ void linphone_reporting_destroy(reporting_session_report_t * report) { if (report->local_metrics.session_description.payload_desc != NULL) ms_free(report->local_metrics.session_description.payload_desc); if (report->remote_metrics.session_description.fmtp != NULL) ms_free(report->remote_metrics.session_description.fmtp); if (report->remote_metrics.session_description.payload_desc != NULL) ms_free(report->remote_metrics.session_description.payload_desc); + if (report->local_metrics.adaptive_algorithm.input != NULL) ms_free(report->local_metrics.adaptive_algorithm.input); + if (report->local_metrics.adaptive_algorithm.output != NULL) ms_free(report->local_metrics.adaptive_algorithm.output); ms_free(report); } diff --git a/coreapi/quality_reporting.h b/coreapi/quality_reporting.h index 651daeace..44704c32a 100644 --- a/coreapi/quality_reporting.h +++ b/coreapi/quality_reporting.h @@ -28,7 +28,7 @@ extern "C"{ /** - * Linphone quality report sub object storing address related information (ip / port / MAC). + * Linphone quality report sub object storing address related information (IP/port/MAC). */ typedef struct reporting_addr { char * ip; @@ -60,15 +60,15 @@ typedef struct reporting_content_metrics { // jitter buffet - optional struct { int adaptive; // constant - int nominal; // no may vary during the call <- average? worst score? - int max; // no may vary during the call <- average? + int nominal; // average + int max; // average int abs_max; // constant } jitter_buffer; // packet loss - optional struct { - float network_packet_loss_rate; - float jitter_buffer_discard_rate; + float network_packet_loss_rate; // average + float jitter_buffer_discard_rate; // average } packet_loss; // delay - optional @@ -93,6 +93,16 @@ typedef struct reporting_content_metrics { float moslq; // no - vary or avg - voip metrics - in [0..4.9] float moscq; // no - vary or avg - voip metrics - in [0..4.9] } quality_estimates; + + // adaptive algorithm - custom extension + struct { + char* input; + char* output; + } adaptive_algorithm; + + // for internal processing + uint8_t rtcp_xr_count; // number of RTCP XR packets received since last report, used to compute average of instantaneous parameters as stated in the RFC 6035 (4.5) + } reporting_content_metrics_t; @@ -131,7 +141,7 @@ void linphone_reporting_destroy(reporting_session_report_t * report); * @param stats_type the media type (LINPHONE_CALL_STATS_AUDIO or LINPHONE_CALL_STATS_VIDEO) * */ -void linphone_reporting_update(LinphoneCall * call, int stats_type); +void linphone_reporting_update_media_info(LinphoneCall * call, int stats_type); /** * Fill IP information about a given call. This function must be called each @@ -148,7 +158,7 @@ void linphone_reporting_update_ip(LinphoneCall * call); * @param call #LinphoneCall object to consider * */ -void linphone_reporting_publish(LinphoneCall* call); +void linphone_reporting_publish_on_call_term(LinphoneCall* call); /** * Update publish report data with fresh RTCP stats, if needed. @@ -156,7 +166,7 @@ void linphone_reporting_publish(LinphoneCall* call); * @param stats_type the media type (LINPHONE_CALL_STATS_AUDIO or LINPHONE_CALL_STATS_VIDEO) * */ -void linphone_reporting_call_stats_updated(LinphoneCall *call, int stats_type); +void linphone_reporting_on_rtcp_received(LinphoneCall *call, int stats_type); #ifdef __cplusplus } diff --git a/mediastreamer2 b/mediastreamer2 index 9c3d93635..8ea457fa6 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 9c3d936356fe4d4c8a1fa8ca57a47598ece44f81 +Subproject commit 8ea457fa65e32c3c943e0bedea6fbdbdc18f7156 From 52d8d8c59af7877ce1e1f6a95628aac9b5030fc8 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 4 Jun 2014 18:10:21 +0200 Subject: [PATCH 70/94] Show RTP profile in call statistics dialog. --- coreapi/linphonecall.c | 6 +- coreapi/linphonecore.h | 7 ++ gtk/call_statistics.ui | 227 ++++++++++++++++++++--------------------- gtk/incall_view.c | 7 +- 4 files changed, 128 insertions(+), 119 deletions(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index c6087580c..aa1786891 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -337,7 +337,7 @@ void linphone_call_increment_local_media_description(LinphoneCall *call){ md->session_ver++; } -static SalMediaProto get_proto_from_call_params(LinphoneCallParams *params) { +static SalMediaProto get_proto_from_call_params(const LinphoneCallParams *params) { if ((params->media_encryption == LinphoneMediaEncryptionSRTP) && params->avpf_enabled) return SalProtoRtpSavpf; if (params->media_encryption == LinphoneMediaEncryptionSRTP) return SalProtoRtpSavp; if (params->avpf_enabled) return SalProtoRtpAvpf; @@ -1280,6 +1280,10 @@ MSVideoSize linphone_call_params_get_received_video_size(const LinphoneCallParam return cp->recv_vsize; } +const char * linphone_call_params_get_rtp_profile(const LinphoneCallParams *cp) { + return sal_media_proto_to_string(get_proto_from_call_params(cp)); +} + /** * @ingroup call_control * Use to know if this call has been configured in low bandwidth mode. diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index b7e297e0a..60813272c 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -429,6 +429,13 @@ LINPHONE_PUBLIC MSVideoSize linphone_call_params_get_sent_video_size(const Linph */ LINPHONE_PUBLIC MSVideoSize linphone_call_params_get_received_video_size(const LinphoneCallParams *cp); +/** + * Gets the RTP profile being used. + * @param[in] cp #LinphoneCallParams object + * @returns The RTP profile. + */ +LINPHONE_PUBLIC const char * linphone_call_params_get_rtp_profile(const LinphoneCallParams *cp); + /* * Note for developers: this enum must be kept synchronized with the SalPrivacy enum declared in sal.h diff --git a/gtk/call_statistics.ui b/gtk/call_statistics.ui index 2412d9793..d53c93a1f 100644 --- a/gtk/call_statistics.ui +++ b/gtk/call_statistics.ui @@ -1,115 +1,67 @@ - + - False 5 Call statistics dialog - + True - False 2 - - - True - False - end - - - - - - gtk-close - True - True - True - False - True - - - False - False - 1 - - - - - False - True - end - 0 - - True - False 0 none True - False 12 True - False - 9 + 10 2 True True - False Audio codec - + 1 + 2 + True - False Video codec - 1 - 2 - + 2 + 3 + True - False Audio IP bandwidth usage - 2 - 3 - + 3 + 4 + True - False - - - 1 - 2 - - - - - True - False 1 @@ -119,9 +71,8 @@ - + True - False 1 @@ -130,22 +81,53 @@ 3 + + + True + + + 1 + 2 + 3 + 4 + + True - False Audio Media connectivity - 4 - 5 - + 5 + 6 + True - False + + + 1 + 2 + 5 + 6 + + + + + True + Video IP bandwidth usage + + + 4 + 5 + + + + + + True 1 @@ -154,120 +136,106 @@ 5 - - - True - False - Video IP bandwidth usage - - - 3 - 4 - - - - - - True - False - - - 1 - 2 - 3 - 4 - - True - False Video Media connectivity - 5 - 6 + 6 + 7 True - False 1 2 - 5 - 6 + 6 + 7 True - False Round trip time - 6 - 7 + 7 + 8 True - False 1 2 - 6 - 7 + 7 + 8 True - False Video resolution received - 7 - 8 + 8 + 9 True - False 1 2 - 7 - 8 + 8 + 9 True - False Video resolution sent - 8 - 9 + 9 + 10 True - False 1 2 - 8 - 9 + 9 + 10 + + + + + True + RTP profile + + + + + + + + True + + + 1 + 2 @@ -277,7 +245,6 @@ True - False <b>Call statistics and information</b> True @@ -289,6 +256,34 @@ 1 + + + True + end + + + + + + gtk-close + True + True + True + True + + + False + False + 1 + + + + + False + end + 0 + + diff --git a/gtk/incall_view.c b/gtk/incall_view.c index 8ab6309f6..382e27746 100644 --- a/gtk/incall_view.c +++ b/gtk/incall_view.c @@ -258,9 +258,12 @@ static void _refresh_call_stats(GtkWidget *callstats, LinphoneCall *call){ gboolean has_video=linphone_call_params_video_enabled(linphone_call_get_current_params(call)); MSVideoSize size_received = linphone_call_params_get_received_video_size(linphone_call_get_current_params(call)); MSVideoSize size_sent = linphone_call_params_get_sent_video_size(linphone_call_get_current_params(call)); - gchar *tmp=g_strdup_printf(_("download: %f\nupload: %f (kbit/s)"), + const char *rtp_profile = linphone_call_params_get_rtp_profile(linphone_call_get_current_params(call)); + gchar *tmp = g_strdup_printf("%s", rtp_profile); + gtk_label_set_markup(GTK_LABEL(linphone_gtk_get_widget(callstats,"rtp_profile")),tmp); + g_free(tmp); + tmp=g_strdup_printf(_("download: %f\nupload: %f (kbit/s)"), as->download_bandwidth,as->upload_bandwidth); - gtk_label_set_markup(GTK_LABEL(linphone_gtk_get_widget(callstats,"audio_bandwidth_usage")),tmp); g_free(tmp); if (has_video){ From fb61e1cf03d07e651bb82a23a61ada68f2680c87 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 5 Jun 2014 10:48:24 +0200 Subject: [PATCH 71/94] Fix AVPF status in the call current params. --- coreapi/linphonecall.c | 56 ++++++++++++++++++++++++++++++++++++------ mediastreamer2 | 2 +- oRTP | 2 +- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index aa1786891..af4ab7b57 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -96,27 +96,63 @@ bool_t linphone_call_get_authentication_token_verified(LinphoneCall *call){ return call->auth_token_verified; } -static bool_t linphone_call_are_all_streams_encrypted(LinphoneCall *call) { +static bool_t linphone_call_all_streams_encrypted(const LinphoneCall *call) { int number_of_encrypted_stream = 0; int number_of_active_stream = 0; if (call) { if (call->audiostream && media_stream_get_state((MediaStream *)call->audiostream) == MSStreamStarted) { number_of_active_stream++; - if(media_stream_is_secured((MediaStream *)call->audiostream)) + if(media_stream_secured((MediaStream *)call->audiostream)) number_of_encrypted_stream++; } if (call->videostream && media_stream_get_state((MediaStream *)call->videostream) == MSStreamStarted) { number_of_active_stream++; - if (media_stream_is_secured((MediaStream *)call->videostream)) + if (media_stream_secured((MediaStream *)call->videostream)) number_of_encrypted_stream++; } } return number_of_active_stream>0 && number_of_active_stream==number_of_encrypted_stream; } +static bool_t linphone_call_all_streams_avpf_enabled(const LinphoneCall *call) { + int nb_active_streams = 0; + int nb_avpf_enabled_streams = 0; + if (call) { + if (call->audiostream && media_stream_get_state((MediaStream *)call->audiostream) == MSStreamStarted) { + nb_active_streams++; + if (media_stream_avpf_enabled((MediaStream *)call->audiostream)) + nb_avpf_enabled_streams++; + } + if (call->videostream && media_stream_get_state((MediaStream *)call->videostream) == MSStreamStarted) { + nb_active_streams++; + if (media_stream_avpf_enabled((MediaStream *)call->videostream)) + nb_avpf_enabled_streams++; + } + } + return ((nb_active_streams > 0) && (nb_active_streams == nb_avpf_enabled_streams)); +} + +static uint8_t linphone_call_get_avpf_rr_interval(const LinphoneCall *call) { + uint8_t rr_interval = 0; + uint8_t stream_rr_interval; + if (call) { + if (call->audiostream && media_stream_get_state((MediaStream *)call->audiostream) == MSStreamStarted) { + stream_rr_interval = media_stream_get_avpf_rr_interval((MediaStream *)call->audiostream); + if (stream_rr_interval > rr_interval) rr_interval = stream_rr_interval; + } + if (call->videostream && media_stream_get_state((MediaStream *)call->videostream) == MSStreamStarted) { + stream_rr_interval = media_stream_get_avpf_rr_interval((MediaStream *)call->videostream); + if (stream_rr_interval > rr_interval) rr_interval = stream_rr_interval; + } + } else { + rr_interval = 5; + } + return rr_interval; +} + static void propagate_encryption_changed(LinphoneCall *call){ LinphoneCore *lc=call->core; - if (!linphone_call_are_all_streams_encrypted(call)) { + if (!linphone_call_all_streams_encrypted(call)) { ms_message("Some streams are not encrypted"); call->current_params.media_encryption=LinphoneMediaEncryptionNone; if (lc->vtable.call_encryption_changed) @@ -988,7 +1024,7 @@ const LinphoneCallParams * linphone_call_get_current_params(LinphoneCall *call){ } #endif - if (linphone_call_are_all_streams_encrypted(call)) { + if (linphone_call_all_streams_encrypted(call)) { if (linphone_call_get_authentication_token(call)) { call->current_params.media_encryption=LinphoneMediaEncryptionZRTP; } else { @@ -997,6 +1033,12 @@ const LinphoneCallParams * linphone_call_get_current_params(LinphoneCall *call){ } else { call->current_params.media_encryption=LinphoneMediaEncryptionNone; } + call->current_params.avpf_enabled = linphone_call_all_streams_avpf_enabled(call); + if (call->current_params.avpf_enabled == TRUE) { + call->current_params.avpf_rr_interval = linphone_call_get_avpf_rr_interval(call); + } else { + call->current_params.avpf_rr_interval = 0; + } return &call->current_params; } @@ -2189,14 +2231,14 @@ void linphone_call_start_media_streams(LinphoneCall *call, bool_t all_inputs_mut params.zid_file=lc->zrtp_secrets_cache; audio_stream_enable_zrtp(call->audiostream,¶ms); #if VIDEO_ENABLED - if (media_stream_is_secured((MediaStream *)call->audiostream) && media_stream_get_state((MediaStream *)call->videostream) == MSStreamStarted) { + if (media_stream_secured((MediaStream *)call->audiostream) && media_stream_get_state((MediaStream *)call->videostream) == MSStreamStarted) { /*audio stream is already encrypted and video stream is active*/ memset(¶ms,0,sizeof(OrtpZrtpParams)); video_stream_enable_zrtp(call->videostream,call->audiostream,¶ms); } #endif }else{ - call->current_params.media_encryption=linphone_call_are_all_streams_encrypted(call) ? + call->current_params.media_encryption=linphone_call_all_streams_encrypted(call) ? LinphoneMediaEncryptionSRTP : LinphoneMediaEncryptionNone; } diff --git a/mediastreamer2 b/mediastreamer2 index 8ea457fa6..406505ddd 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 8ea457fa65e32c3c943e0bedea6fbdbdc18f7156 +Subproject commit 406505dddac08300d937dd5f0f1fd3e701267287 diff --git a/oRTP b/oRTP index a9ffd72d7..30e4d5b1a 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit a9ffd72d73d459e531fad094d18eb18f67870aba +Subproject commit 30e4d5b1abf41c7d96825a3ca194eb47b5a45c1b From 3bf28e79af7946fca345207ae32bce1c4f10f5b2 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 5 Jun 2014 10:53:36 +0200 Subject: [PATCH 72/94] Do not include rtcp-xr and rtcp-fb attributes for inactive medias in the SDP. --- coreapi/bellesip_sal/sal_sdp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coreapi/bellesip_sal/sal_sdp.c b/coreapi/bellesip_sal/sal_sdp.c index 650235399..268fbc480 100644 --- a/coreapi/bellesip_sal/sal_sdp.c +++ b/coreapi/bellesip_sal/sal_sdp.c @@ -266,11 +266,11 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session } } - if ((stream->proto == SalProtoRtpAvpf) || (stream->proto == SalProtoRtpSavpf)) { + if ((rtp_port != 0) && ((stream->proto == SalProtoRtpAvpf) || (stream->proto == SalProtoRtpSavpf))) { add_rtcp_fb_attributes(media_desc, md, stream); } - if (stream->rtcp_xr.enabled == TRUE) { + if ((rtp_port != 0) && (stream->rtcp_xr.enabled == TRUE)) { char sastr[1024] = {0}; char mastr[1024] = {0}; size_t saoff = 0; From 2aed7112775507350ff8c12df93ab8b1f7a42fb3 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 5 Jun 2014 14:15:16 +0200 Subject: [PATCH 73/94] Update oRTP and ms2 submodules. --- mediastreamer2 | 2 +- oRTP | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mediastreamer2 b/mediastreamer2 index 406505ddd..f7eab3265 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 406505dddac08300d937dd5f0f1fd3e701267287 +Subproject commit f7eab3265577feb885dba012d34a10f1b9be072b diff --git a/oRTP b/oRTP index 30e4d5b1a..ec4c194c6 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 30e4d5b1abf41c7d96825a3ca194eb47b5a45c1b +Subproject commit ec4c194c62a9657f075d07dc2495f98914ee0b8b From f9c01ebdb429e9bb79601a6b53bf24f89a3b5b85 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Thu, 5 Jun 2014 15:59:49 +0200 Subject: [PATCH 74/94] fix ICE status not updated at callee side in case of video mline rejected. add new tests. --- coreapi/bellesip_sal/sal_impl.c | 2 + coreapi/linphonecall.c | 4 +- coreapi/linphonecore.c | 1 + coreapi/misc.c | 31 ++++++------- coreapi/quality_reporting.c | 15 ++++--- tester/call_tester.c | 80 ++++++++++++++++++++++++++++++--- 6 files changed, 104 insertions(+), 29 deletions(-) diff --git a/coreapi/bellesip_sal/sal_impl.c b/coreapi/bellesip_sal/sal_impl.c index c5040be3a..6896496f7 100644 --- a/coreapi/bellesip_sal/sal_impl.c +++ b/coreapi/bellesip_sal/sal_impl.c @@ -423,6 +423,8 @@ static void process_auth_requested(void *sal, belle_sip_auth_event_t *event) { Sal * sal_init(){ belle_sip_listener_callbacks_t listener_callbacks; Sal * sal=ms_new0(Sal,1); + + /*belle_sip_object_enable_marshal_check(TRUE);*/ sal->auto_contacts=TRUE; /*first create the stack, which initializes the belle-sip object's pool for this thread*/ diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index af4ab7b57..a1172229c 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -718,7 +718,7 @@ LinphoneCall * linphone_call_new_incoming(LinphoneCore *lc, LinphoneAddress *fro call->current_params.privacy=(LinphonePrivacyMask)sal_op_get_privacy(call->op); /*set video support */ md=sal_call_get_remote_media_description(op); - call->params.has_video = !!lc->video_policy.automatically_accept; + call->params.has_video = lc->video_policy.automatically_accept; if (md) { // It is licit to receive an INVITE without SDP // In this case WE chose the media parameters according to policy. @@ -1582,7 +1582,7 @@ int linphone_call_prepare_ice(LinphoneCall *call, bool_t incoming_offer){ if ((linphone_core_get_firewall_policy(call->core) == LinphonePolicyUseIce) && (call->ice_session != NULL)){ if (incoming_offer){ remote=sal_call_get_remote_media_description(call->op); - has_video=linphone_core_media_description_contains_video_stream(remote); + has_video=call->params.has_video && linphone_core_media_description_contains_video_stream(remote); }else has_video=call->params.has_video; _linphone_call_prepare_ice_for_stream(call,0,TRUE); diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 6c79d7c71..0183979a5 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -3433,6 +3433,7 @@ int linphone_core_accept_call_with_params(LinphoneCore *lc, LinphoneCall *call, call->params.media_encryption = LinphoneMediaEncryptionSRTP; } } + linphone_call_prepare_ice(call,TRUE); linphone_call_make_local_media_description(lc,call); sal_call_set_local_media_description(call->op,call->localdesc); sal_op_set_sent_custom_header(call->op,params->custom_headers); diff --git a/coreapi/misc.c b/coreapi/misc.c index 57ebaa32e..2a86db03a 100644 --- a/coreapi/misc.c +++ b/coreapi/misc.c @@ -803,6 +803,13 @@ static void get_default_addr_and_port(uint16_t componentID, const SalMediaDescri if ((*addr)[0] == '\0') *addr = md->addr; } +static void clear_ice_check_list(LinphoneCall *call, IceCheckList *removed){ + if (call->audiostream && call->audiostream->ms.ice_check_list==removed) + call->audiostream->ms.ice_check_list=NULL; + if (call->videostream && call->videostream->ms.ice_check_list==removed) + call->videostream->ms.ice_check_list=NULL; +} + void linphone_core_update_ice_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) { bool_t ice_restarted = FALSE; @@ -853,6 +860,7 @@ void linphone_core_update_ice_from_remote_media_description(LinphoneCall *call, for (i = 0; i < md->n_total_streams; i++) { const SalStreamDescription *stream = &md->streams[i]; IceCheckList *cl = ice_session_check_list(call->ice_session, i); + /* if ((cl == NULL) && (i < md->n_active_streams)) { cl = ice_check_list_new(); ice_session_add_check_list(call->ice_session, cl); @@ -867,16 +875,13 @@ void linphone_core_update_ice_from_remote_media_description(LinphoneCall *call, break; } } + */ + if (cl==NULL) continue; if (stream->ice_mismatch == TRUE) { ice_check_list_set_state(cl, ICL_Failed); } else if (stream->rtp_port == 0) { ice_session_remove_check_list(call->ice_session, cl); -#ifdef VIDEO_ENABLED - if (stream->type==SalVideo && call->videostream){ - video_stream_stop(call->videostream); - call->videostream=NULL; - } -#endif + clear_ice_check_list(call,cl); } else { if ((stream->ice_pwd[0] != '\0') && (stream->ice_ufrag[0] != '\0')) ice_check_list_set_remote_credentials(cl, stream->ice_ufrag, stream->ice_pwd); @@ -916,10 +921,7 @@ void linphone_core_update_ice_from_remote_media_description(LinphoneCall *call, for (i = ice_session_nb_check_lists(call->ice_session); i > md->n_active_streams; i--) { IceCheckList *removed=ice_session_check_list(call->ice_session, i - 1); ice_session_remove_check_list(call->ice_session, removed); - if (call->audiostream && call->audiostream->ms.ice_check_list==removed) - call->audiostream->ms.ice_check_list=NULL; - if (call->videostream && call->videostream->ms.ice_check_list==removed) - call->videostream->ms.ice_check_list=NULL; + clear_ice_check_list(call,removed); } ice_session_check_mismatch(call->ice_session); } else { @@ -932,12 +934,11 @@ void linphone_core_update_ice_from_remote_media_description(LinphoneCall *call, } } -bool_t linphone_core_media_description_contains_video_stream(const SalMediaDescription *md) -{ +bool_t linphone_core_media_description_contains_video_stream(const SalMediaDescription *md){ int i; - for (i = 0; i < md->n_active_streams; i++) { - if (md->streams[i].type == SalVideo) + for (i = 0; i < md->n_total_streams; i++) { + if (md->streams[i].type == SalVideo && md->streams[i].rtp_port!=0) return TRUE; } return FALSE; @@ -1502,7 +1503,7 @@ const MSCryptoSuite * linphone_core_get_srtp_crypto_suites(LinphoneCore *lc){ np.params=params; suite=ms_crypto_suite_build_from_name_params(&np); if (suite!=MS_CRYPTO_SUITE_INVALID){ - result=ms_realloc(result,found+1+1); + result=ms_realloc(result,(found+2)*sizeof(MSCryptoSuite)); result[found]=suite; result[found+1]=MS_CRYPTO_SUITE_INVALID; found++; diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c index 50c283cf0..164653e18 100644 --- a/coreapi/quality_reporting.c +++ b/coreapi/quality_reporting.c @@ -479,7 +479,7 @@ void linphone_reporting_update_media_info(LinphoneCall * call, int stats_type) { void linphone_reporting_on_rtcp_received(LinphoneCall *call, int stats_type) { reporting_session_report_t * report = call->log->reports[stats_type]; reporting_content_metrics_t * metrics = NULL; - + MSQosAnalyser *analyser=NULL; LinphoneCallStats stats = call->stats[stats_type]; mblk_t *block = NULL; @@ -497,11 +497,14 @@ void linphone_reporting_on_rtcp_received(LinphoneCall *call, int stats_type) { block = stats.sent_rtcp; } } - - ms_qos_analyser_set_on_action_suggested( - ms_bitrate_controller_get_qos_analyser(call->audiostream->ms.rc), - qos_analyser_on_action_suggested, - &report->local_metrics); + if (call->audiostream->ms.rc){ + analyser=ms_bitrate_controller_get_qos_analyser(call->audiostream->ms.rc); + if (analyser){ + ms_qos_analyser_set_on_action_suggested(analyser, + qos_analyser_on_action_suggested, + &report->local_metrics); + } + } if (block != NULL) { switch (rtcp_XR_get_block_type(block)) { diff --git a/tester/call_tester.c b/tester/call_tester.c index a59313626..23c92ef52 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -552,20 +552,23 @@ static void call_with_no_sdp(void) { static bool_t check_ice(LinphoneCoreManager* caller, LinphoneCoreManager* callee, LinphoneIceState state) { LinphoneCall *c1,*c2; - bool_t success=FALSE; + bool_t audio_success=FALSE; + bool_t video_success=FALSE; int i; + bool_t video_enabled; c1=linphone_core_get_current_call(caller->lc); c2=linphone_core_get_current_call(callee->lc); CU_ASSERT_PTR_NOT_NULL(c1); CU_ASSERT_PTR_NOT_NULL(c2); - + CU_ASSERT_EQUAL(linphone_call_params_video_enabled(linphone_call_get_current_params(c1)),linphone_call_params_video_enabled(linphone_call_get_current_params(c2))); + video_enabled=linphone_call_params_video_enabled(linphone_call_get_current_params(c1)); for (i=0;i<200;i++){ if ((c1 != NULL) && (c2 != NULL)) { - if (linphone_call_get_audio_stats(c1)->ice_state==LinphoneIceStateHostConnection && - linphone_call_get_audio_stats(c2)->ice_state==LinphoneIceStateHostConnection ){ - success=TRUE; + if (linphone_call_get_audio_stats(c1)->ice_state==state && + linphone_call_get_audio_stats(c2)->ice_state==state ){ + audio_success=TRUE; break; } linphone_core_iterate(caller->lc); @@ -573,6 +576,21 @@ static bool_t check_ice(LinphoneCoreManager* caller, LinphoneCoreManager* callee } ms_usleep(50000); } + + if (video_enabled){ + for (i=0;i<200;i++){ + if ((c1 != NULL) && (c2 != NULL)) { + if (linphone_call_get_video_stats(c1)->ice_state==state && + linphone_call_get_video_stats(c2)->ice_state==state ){ + video_success=TRUE; + break; + } + linphone_core_iterate(caller->lc); + linphone_core_iterate(callee->lc); + } + ms_usleep(50000); + } + } /*make sure encryption mode are preserved*/ if (c1) { @@ -584,7 +602,7 @@ static bool_t check_ice(LinphoneCoreManager* caller, LinphoneCoreManager* callee CU_ASSERT_EQUAL(linphone_call_params_get_media_encryption(call_param),linphone_core_get_media_encryption(callee->lc)); } - return success; + return video_enabled ? audio_success && video_success : audio_success; } static void _call_with_ice_base(LinphoneCoreManager* pauline,LinphoneCoreManager* marie, bool_t caller_with_ice, bool_t callee_with_ice, bool_t random_ports) { @@ -982,6 +1000,54 @@ static void video_call_no_sdp(void) { linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); } + +static void call_with_ice_video_to_novideo(void) { + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + LinphoneVideoPolicy vpol={0}; + vpol.automatically_initiate=TRUE; + linphone_core_set_video_policy(pauline->lc,&vpol); + vpol.automatically_initiate=FALSE; + linphone_core_set_video_policy(marie->lc,&vpol); + _call_with_ice_base(pauline,marie,TRUE,TRUE,TRUE); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +static void call_with_ice_video_added(void) { + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + LinphoneVideoPolicy vpol={0}; + linphone_core_set_video_policy(pauline->lc,&vpol); + linphone_core_set_video_policy(marie->lc,&vpol); + + linphone_core_set_firewall_policy(marie->lc,LinphonePolicyUseIce); + linphone_core_set_stun_server(marie->lc,"stun.linphone.org"); + + + linphone_core_set_firewall_policy(pauline->lc,LinphonePolicyUseIce); + linphone_core_set_stun_server(pauline->lc,"stun.linphone.org"); + + + if (1){ + linphone_core_set_audio_port(marie->lc,-1); + linphone_core_set_video_port(marie->lc,-1); + linphone_core_set_audio_port(pauline->lc,-1); + linphone_core_set_video_port(pauline->lc,-1); + } + + CU_ASSERT_TRUE(call(pauline,marie)); + CU_ASSERT_TRUE(check_ice(pauline,marie,LinphoneIceStateHostConnection)); + /*wait for ICE reINVITEs to complete*/ + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallStreamsRunning,2) + && + wait_for(pauline->lc,pauline->lc,&marie->stat.number_of_LinphoneCallStreamsRunning,2)); + CU_ASSERT_TRUE(add_video(pauline,marie)); + CU_ASSERT_TRUE(check_ice(pauline,marie,LinphoneIceStateHostConnection)); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + #endif /*VIDEO_ENABLED*/ static void _call_with_media_relay(bool_t random_ports) { @@ -2278,6 +2344,8 @@ test_t call_tests[] = { { "Call with video added (random ports)", call_with_video_added_random_ports }, { "Call with video declined",call_with_declined_video}, { "Call with multiple early media", multiple_early_media }, + { "Call with ICE from video to non-video", call_with_ice_video_to_novideo}, + { "Call with ICE and video added", call_with_ice_video_added }, #endif { "SRTP ice call", srtp_ice_call }, { "ZRTP ice call", zrtp_ice_call }, From 22e2cb518f80b71922579a4c378dcaf2bc3518b8 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Thu, 5 Jun 2014 11:01:50 +0200 Subject: [PATCH 75/94] Quality reporting: rename parameters send_statistics to quality_reporting_enabled and statistics_collector to quality_repotring_collector --- coreapi/linphonecore.h | 8 ++--- coreapi/private.h | 4 +-- coreapi/proxy.c | 42 +++++++++++++-------------- coreapi/quality_reporting.c | 4 +-- tester/call_tester.c | 58 ++++++++++++++++++------------------- tester/rcfiles/marie_rc | 4 +-- 6 files changed, 60 insertions(+), 60 deletions(-) diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 60813272c..372caeb70 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -840,10 +840,10 @@ LINPHONE_PUBLIC void linphone_proxy_config_set_dial_prefix(LinphoneProxyConfig * * @param val if true, quality statistics publish will be stored and sent to the collector * */ -LINPHONE_PUBLIC void linphone_proxy_config_enable_statistics(LinphoneProxyConfig *cfg, bool_t val); -LINPHONE_PUBLIC bool_t linphone_proxy_config_send_statistics_enabled(LinphoneProxyConfig *cfg); -LINPHONE_PUBLIC void linphone_proxy_config_set_statistics_collector(LinphoneProxyConfig *cfg, const char *collector); -LINPHONE_PUBLIC const char *linphone_proxy_config_get_statistics_collector(const LinphoneProxyConfig *obj); +LINPHONE_PUBLIC void linphone_proxy_config_enable_quality_reporting(LinphoneProxyConfig *cfg, bool_t val); +LINPHONE_PUBLIC bool_t linphone_proxy_config_quality_reporting_enabled(LinphoneProxyConfig *cfg); +LINPHONE_PUBLIC void linphone_proxy_config_set_quality_reporting_collector(LinphoneProxyConfig *cfg, const char *collector); +LINPHONE_PUBLIC const char *linphone_proxy_config_get_quality_reporting_collector(const LinphoneProxyConfig *obj); /** * Get the registration state of the given proxy config. diff --git a/coreapi/private.h b/coreapi/private.h index 8de760b85..ed9e78878 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -415,7 +415,7 @@ struct _LinphoneProxyConfig char *reg_proxy; char *reg_identity; char *reg_route; - char *statistics_collector; + char *quality_reporting_collector; char *realm; char *contact_params; char *contact_uri_params; @@ -433,7 +433,7 @@ struct _LinphoneProxyConfig bool_t publish; bool_t dial_escape_plus; bool_t send_publish; - bool_t send_statistics; + bool_t quality_reporting_enabled; bool_t avpf_enabled; bool_t pad; uint8_t avpf_rr_interval; diff --git a/coreapi/proxy.c b/coreapi/proxy.c index dde023bc4..a72e3523a 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -59,7 +59,7 @@ bool_t linphone_proxy_config_is_server_config_changed(const LinphoneProxyConfig* LinphoneAddress *current_identity=obj->reg_identity?linphone_address_new(obj->reg_identity):NULL; LinphoneAddress *current_proxy=obj->reg_proxy?linphone_address_new(obj->reg_proxy):NULL; bool_t result=FALSE; - + if (!linphone_proxy_config_address_equal(obj->saved_identity,current_identity)){ result=TRUE; goto end; @@ -94,7 +94,7 @@ static void linphone_proxy_config_init(LinphoneCore* lc, LinphoneProxyConfig *ob const char *identity = lc ? lp_config_get_default_string(lc->config, "proxy", "reg_identity", NULL) : NULL; const char *proxy = lc ? lp_config_get_default_string(lc->config, "proxy", "reg_proxy", NULL) : NULL; const char *route = lc ? lp_config_get_default_string(lc->config, "proxy", "reg_route", NULL) : NULL; - const char *statistics_collector = lc ? lp_config_get_default_string(lc->config, "proxy", "statistics_collector", NULL) : NULL; + const char *quality_reporting_collector = lc ? lp_config_get_default_string(lc->config, "proxy", "quality_reporting_collector", NULL) : NULL; const char *contact_params = lc ? lp_config_get_default_string(lc->config, "proxy", "contact_parameters", NULL) : NULL; const char *contact_uri_params = lc ? lp_config_get_default_string(lc->config, "proxy", "contact_uri_parameters", NULL) : NULL; @@ -108,8 +108,8 @@ static void linphone_proxy_config_init(LinphoneCore* lc, LinphoneProxyConfig *ob obj->reg_identity = identity ? ms_strdup(identity) : NULL; obj->reg_proxy = proxy ? ms_strdup(proxy) : NULL; obj->reg_route = route ? ms_strdup(route) : NULL; - obj->statistics_collector = statistics_collector ? ms_strdup(statistics_collector) : NULL; - obj->send_statistics = lc ? lp_config_get_default_int(lc->config, "proxy", "send_statistics", 0) : 0; + obj->quality_reporting_collector = quality_reporting_collector ? ms_strdup(quality_reporting_collector) : NULL; + obj->quality_reporting_enabled = lc ? lp_config_get_default_int(lc->config, "proxy", "quality_reporting_enabled", 0) : 0; obj->contact_params = contact_params ? ms_strdup(contact_params) : NULL; obj->contact_uri_params = contact_uri_params ? ms_strdup(contact_uri_params) : NULL; obj->avpf_enabled = lc ? lp_config_get_default_int(lc->config, "proxy", "avpf", 0) : 0; @@ -147,7 +147,7 @@ void linphone_proxy_config_destroy(LinphoneProxyConfig *obj){ if (obj->reg_proxy!=NULL) ms_free(obj->reg_proxy); if (obj->reg_identity!=NULL) ms_free(obj->reg_identity); if (obj->reg_route!=NULL) ms_free(obj->reg_route); - if (obj->statistics_collector!=NULL) ms_free(obj->statistics_collector); + if (obj->quality_reporting_collector!=NULL) ms_free(obj->quality_reporting_collector); if (obj->ssctx!=NULL) sip_setup_context_free(obj->ssctx); if (obj->realm!=NULL) ms_free(obj->realm); if (obj->type!=NULL) ms_free(obj->type); @@ -480,16 +480,16 @@ bool_t linphone_proxy_config_get_dial_escape_plus(const LinphoneProxyConfig *cfg return cfg->dial_escape_plus; } -void linphone_proxy_config_enable_statistics(LinphoneProxyConfig *cfg, bool_t val){ - cfg->send_statistics = val; +void linphone_proxy_config_enable_quality_reporting(LinphoneProxyConfig *cfg, bool_t val){ + cfg->quality_reporting_enabled = val; } -bool_t linphone_proxy_config_send_statistics_enabled(LinphoneProxyConfig *cfg){ +bool_t linphone_proxy_config_quality_reporting_enabled(LinphoneProxyConfig *cfg){ // ensure that collector address is set too! - return cfg->send_statistics && cfg->statistics_collector != NULL; + return cfg->quality_reporting_enabled && cfg->quality_reporting_collector != NULL; } -void linphone_proxy_config_set_statistics_collector(LinphoneProxyConfig *cfg, const char *collector){ +void linphone_proxy_config_set_quality_reporting_collector(LinphoneProxyConfig *cfg, const char *collector){ if (collector!=NULL && strlen(collector)>0){ LinphoneAddress *addr=linphone_address_new(collector); if (!addr || linphone_address_get_username(addr)==NULL){ @@ -497,16 +497,16 @@ void linphone_proxy_config_set_statistics_collector(LinphoneProxyConfig *cfg, co if (addr) linphone_address_destroy(addr); } else { - if (cfg->statistics_collector != NULL) - ms_free(cfg->statistics_collector); - cfg->statistics_collector = ms_strdup(collector); + if (cfg->quality_reporting_collector != NULL) + ms_free(cfg->quality_reporting_collector); + cfg->quality_reporting_collector = ms_strdup(collector); linphone_address_destroy(addr); } } } -const char *linphone_proxy_config_get_statistics_collector(const LinphoneProxyConfig *cfg){ - return cfg->statistics_collector; +const char *linphone_proxy_config_get_quality_reporting_collector(const LinphoneProxyConfig *cfg){ + return cfg->quality_reporting_collector; } @@ -1174,8 +1174,8 @@ void linphone_proxy_config_write_to_config_file(LpConfig *config, LinphoneProxyC if (obj->reg_route!=NULL){ lp_config_set_string(config,key,"reg_route",obj->reg_route); } - if (obj->statistics_collector!=NULL){ - lp_config_set_string(config,key,"statistics_collector",obj->statistics_collector); + if (obj->quality_reporting_collector!=NULL){ + lp_config_set_string(config,key,"quality_reporting_collector",obj->quality_reporting_collector); } if (obj->reg_identity!=NULL){ lp_config_set_string(config,key,"reg_identity",obj->reg_identity); @@ -1192,7 +1192,7 @@ void linphone_proxy_config_write_to_config_file(LpConfig *config, LinphoneProxyC lp_config_set_int(config, key, "avpf", obj->avpf_enabled); lp_config_set_int(config, key, "avpf_rr_interval", obj->avpf_rr_interval); lp_config_set_int(config,key,"dial_escape_plus",obj->dial_escape_plus); - lp_config_set_int(config,key,"send_statistics",obj->send_statistics); + lp_config_set_int(config,key,"quality_reporting_enabled",obj->quality_reporting_enabled); lp_config_set_string(config,key,"dial_prefix",obj->dial_prefix); lp_config_set_int(config,key,"privacy",obj->privacy); } @@ -1224,9 +1224,9 @@ LinphoneProxyConfig *linphone_proxy_config_new_from_config_file(LpConfig *config tmp=lp_config_get_string(config,key,"reg_route",NULL); if (tmp!=NULL) linphone_proxy_config_set_route(cfg,tmp); - tmp=lp_config_get_string(config,key,"statistics_collector",NULL); - if (tmp!=NULL) linphone_proxy_config_set_statistics_collector(cfg,tmp); - linphone_proxy_config_enable_statistics(cfg,lp_config_get_int(config,key,"send_statistics",0)); + tmp=lp_config_get_string(config,key,"quality_reporting_collector",NULL); + if (tmp!=NULL) linphone_proxy_config_set_quality_reporting_collector(cfg,tmp); + linphone_proxy_config_enable_quality_reporting(cfg,lp_config_get_int(config,key,"quality_reporting_enabled",0)); linphone_proxy_config_set_contact_parameters(cfg,lp_config_get_string(config,key,"contact_parameters",NULL)); diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c index 164653e18..c9c5e7fd7 100644 --- a/coreapi/quality_reporting.c +++ b/coreapi/quality_reporting.c @@ -299,7 +299,7 @@ static void send_report(const LinphoneCall* call, reporting_session_report_t * r return; } - addr = linphone_address_new(call->dest_proxy->statistics_collector); + addr = linphone_address_new(linphone_proxy_config_get_quality_reporting_collector(call->dest_proxy)); if (addr == NULL) { ms_warning("Asked to submit reporting statistics but no collector address found"); return; @@ -384,7 +384,7 @@ static void update_ip(LinphoneCall * call, int stats_type) { } static bool_t is_reporting_enabled(const LinphoneCall * call) { - return (call->dest_proxy != NULL && linphone_proxy_config_send_statistics_enabled(call->dest_proxy)); + return (call->dest_proxy != NULL && linphone_proxy_config_quality_reporting_enabled(call->dest_proxy)); } static void qos_analyser_on_action_suggested(void *user_data, const char * input, const char * output){ diff --git a/tester/call_tester.c b/tester/call_tester.c index 23c92ef52..f1b94bbd2 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -259,17 +259,17 @@ static void call_with_specified_codec_bitrate(void) { ms_warning("opus codec not supported, test skipped."); goto end; } - + disable_all_codecs_except_one(marie->lc,"opus"); disable_all_codecs_except_one(pauline->lc,"opus"); - + linphone_core_set_payload_type_bitrate(marie->lc, linphone_core_find_payload_type(marie->lc,"opus",48000,-1), 50); linphone_core_set_payload_type_bitrate(pauline->lc, linphone_core_find_payload_type(pauline->lc,"opus",48000,-1), 24); - + CU_ASSERT_TRUE((call_ok=call(pauline,marie))); if (!call_ok) goto end; liblinphone_tester_check_rtcp(marie,pauline); @@ -663,9 +663,9 @@ static void call_with_ice_no_sdp(void){ linphone_core_set_firewall_policy(pauline->lc,LinphonePolicyUseIce); linphone_core_set_stun_server(pauline->lc,"stun.linphone.org"); - + call(pauline,marie); - + liblinphone_tester_check_rtcp(marie,pauline); linphone_core_manager_destroy(marie); @@ -2136,7 +2136,7 @@ static void call_rejected_without_403_because_wrong_credentials_no_auth_req_cb() call_rejected_because_wrong_credentials_with_params("tester-no-403",FALSE); } -void create_call_for_statistics_tests( +void create_call_for_quality_reporting_tests( LinphoneCoreManager* marie, LinphoneCoreManager* pauline, LinphoneCall** call_marie, @@ -2148,20 +2148,20 @@ void create_call_for_statistics_tests( CU_ASSERT_PTR_NOT_NULL(*call_pauline); } -static void statistics_not_used_without_config() { +static void quality_reporting_not_used_without_config() { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); LinphoneCall* call_marie = NULL; LinphoneCall* call_pauline = NULL; - create_call_for_statistics_tests(marie, pauline, &call_marie, &call_pauline); + create_call_for_quality_reporting_tests(marie, pauline, &call_marie, &call_pauline); // marie has stats collection enabled since pauline has not - CU_ASSERT_TRUE(linphone_proxy_config_send_statistics_enabled(call_marie->dest_proxy)); - CU_ASSERT_FALSE(linphone_proxy_config_send_statistics_enabled(call_pauline->dest_proxy)); + CU_ASSERT_TRUE(linphone_proxy_config_quality_reporting_enabled(call_marie->dest_proxy)); + CU_ASSERT_FALSE(linphone_proxy_config_quality_reporting_enabled(call_pauline->dest_proxy)); CU_ASSERT_EQUAL(strcmp("sip:collector@sip.example.org", - linphone_proxy_config_get_statistics_collector(call_marie->dest_proxy)), 0); + linphone_proxy_config_get_quality_reporting_collector(call_marie->dest_proxy)), 0); // this field should be already filled CU_ASSERT_PTR_NOT_NULL(call_marie->log->reports[0]->info.local_addr.ip); @@ -2173,7 +2173,7 @@ static void statistics_not_used_without_config() { linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); } -static void statistics_not_sent_if_call_not_started() { +static void quality_reporting_not_sent_if_call_not_started() { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); LinphoneCallLog* out_call_log; @@ -2201,14 +2201,14 @@ static void statistics_not_sent_if_call_not_started() { linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); } -static void statistics_sent_at_call_termination() { +static void quality_reporting_at_call_termination() { // int return_code = -1; LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); LinphoneCall* call_marie = NULL; LinphoneCall* call_pauline = NULL; - create_call_for_statistics_tests(marie, pauline, &call_marie, &call_pauline); + create_call_for_quality_reporting_tests(marie, pauline, &call_marie, &call_pauline); linphone_core_terminate_all_calls(marie->lc); CU_ASSERT_TRUE(wait_for_until(marie->lc,pauline->lc,&marie->stat.number_of_LinphoneCallReleased,1, 10000)); @@ -2243,12 +2243,12 @@ static void multiple_early_media(void) { int dummy=0; char ringbackpath[256]; snprintf(ringbackpath,sizeof(ringbackpath), "%s/sounds/hello8000.wav" /*use hello because rinback is too short*/, liblinphone_tester_file_prefix); - + pol.automatically_accept=1; pol.automatically_initiate=1; - + linphone_core_enable_video(pauline->lc,TRUE,TRUE); - + linphone_core_enable_video(marie1->lc,TRUE,TRUE); linphone_core_set_video_policy(marie1->lc,&pol); /*use playfile for marie1 to avoid locking on capture card*/ @@ -2262,44 +2262,44 @@ static void multiple_early_media(void) { /*use playfile for marie2 to avoid locking on capture card*/ linphone_core_use_files(marie2->lc,TRUE); linphone_core_set_play_file(marie2->lc,ringbackpath); - - + + lcs=ms_list_append(lcs,marie1->lc); lcs=ms_list_append(lcs,marie2->lc); lcs=ms_list_append(lcs,pauline->lc); linphone_call_params_enable_early_media_sending(params,TRUE); linphone_call_params_enable_video(params,TRUE); - + linphone_core_invite_address_with_params(pauline->lc,marie1->identity,params); linphone_call_params_destroy(params); CU_ASSERT_TRUE(wait_for_list(lcs, &marie1->stat.number_of_LinphoneCallIncomingEarlyMedia,1,3000)); CU_ASSERT_TRUE(wait_for_list(lcs, &marie2->stat.number_of_LinphoneCallIncomingEarlyMedia,1,3000)); CU_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallOutgoingEarlyMedia,1,3000)); - + pauline_call=linphone_core_get_current_call(pauline->lc); marie1_call=linphone_core_get_current_call(marie1->lc); marie2_call=linphone_core_get_current_call(marie2->lc); - + /*wait a bit that streams are established*/ wait_for_list(lcs,&dummy,1,6000); CU_ASSERT_TRUE(linphone_call_get_audio_stats(pauline_call)->download_bandwidth>70); CU_ASSERT_TRUE(linphone_call_get_audio_stats(marie1_call)->download_bandwidth>70); CU_ASSERT_TRUE(linphone_call_get_audio_stats(marie2_call)->download_bandwidth>70); - + linphone_core_accept_call(marie1->lc,linphone_core_get_current_call(marie1->lc)); CU_ASSERT_TRUE(wait_for_list(lcs,&marie1->stat.number_of_LinphoneCallStreamsRunning,1,3000)); CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallStreamsRunning,1,3000)); - + /*marie2 should get her call terminated*/ CU_ASSERT_TRUE(wait_for_list(lcs,&marie2->stat.number_of_LinphoneCallEnd,1,1000)); - + /*wait a bit that streams are established*/ wait_for_list(lcs,&dummy,1,1000); CU_ASSERT_TRUE(linphone_call_get_audio_stats(pauline_call)->download_bandwidth>71); CU_ASSERT_TRUE(linphone_call_get_audio_stats(marie1_call)->download_bandwidth>71); - + 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,&marie1->stat.number_of_LinphoneCallEnd,1,1000)); @@ -2374,9 +2374,9 @@ test_t call_tests[] = { { "Call established with rejected incoming RE-INVITE", call_established_with_rejected_incoming_reinvite }, { "Call established with rejected RE-INVITE in error", call_established_with_rejected_reinvite_with_error}, { "Call redirected by callee", call_redirect}, - { "Call statistics not used if no config", statistics_not_used_without_config}, - { "Call statistics not sent if call did not start", statistics_not_sent_if_call_not_started}, - { "Call statistics sent if call ended normally", statistics_sent_at_call_termination}, + { "Call quality reporting not used if no config", quality_reporting_not_used_without_config}, + { "Call quality reporting not sent if call did not start", quality_reporting_not_sent_if_call_not_started}, + { "Call quality reporting sent if call ended normally", quality_reporting_at_call_termination}, { "Call with specified codec bitrate", call_with_specified_codec_bitrate} }; diff --git a/tester/rcfiles/marie_rc b/tester/rcfiles/marie_rc index e5cd7a3b5..f4f9aa793 100644 --- a/tester/rcfiles/marie_rc +++ b/tester/rcfiles/marie_rc @@ -22,8 +22,8 @@ reg_expires=3600 reg_sendregister=1 publish=0 dial_escape_plus=0 -statistics_collector=sip:collector@sip.example.org -send_statistics=1 +quality_reporting_collector=sip:collector@sip.example.org +quality_reporting_enabled=1 [friend_0] url="Paupoche" From ce7a644616b694f9a612bb04e85b0e1537b01ed6 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Thu, 5 Jun 2014 11:57:51 +0200 Subject: [PATCH 76/94] Quality reporting: add possibility to send interval reports to a given spacing interval during a call --- coreapi/linphonecall.c | 2 +- coreapi/linphonecore.h | 52 ++++++++++++++++++++++++++++++++----- coreapi/private.h | 1 + coreapi/proxy.c | 23 +++++++++++----- coreapi/quality_reporting.c | 43 +++++++++++++++++++++++++----- coreapi/quality_reporting.h | 15 +++++++++-- 6 files changed, 114 insertions(+), 22 deletions(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index a1172229c..2c6c188b4 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -907,7 +907,7 @@ void linphone_call_set_state(LinphoneCall *call, LinphoneCallState cstate, const if (cstate==LinphoneCallEnd){ if (call->log->status == LinphoneCallSuccess) - linphone_reporting_publish_on_call_term(call); + linphone_reporting_publish_session_report(call); } if (cstate==LinphoneCallReleased){ diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 372caeb70..c3ebf9aaf 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -834,16 +834,54 @@ LINPHONE_PUBLIC int linphone_proxy_config_get_publish_expires(const LinphoneProx LINPHONE_PUBLIC void linphone_proxy_config_set_dial_escape_plus(LinphoneProxyConfig *cfg, bool_t val); LINPHONE_PUBLIC void linphone_proxy_config_set_dial_prefix(LinphoneProxyConfig *cfg, const char *prefix); -/** - * Indicates either or not, quality statistics during call should be stored and sent to a collector at termination. - * @param cfg #LinphoneProxyConfig object - * @param val if true, quality statistics publish will be stored and sent to the collector - * + /** + * Indicates whether quality statistics during call should be stored and sent to a collector according to RFC 6035. + * @param[in] cfg #LinphoneProxyConfig object + * @param[in] enable True to sotre quality statistics and sent them to the collector, false to disable it. + */ +LINPHONE_PUBLIC void linphone_proxy_config_enable_quality_reporting(LinphoneProxyConfig *cfg, bool_t enable); + +/** + * Indicates whether quality statistics during call should be stored and sent to a collector according to RFC 6035. + * @param[in] cfg #LinphoneProxyConfig object + * @return True if quality repotring is enabled, false otherwise. */ -LINPHONE_PUBLIC void linphone_proxy_config_enable_quality_reporting(LinphoneProxyConfig *cfg, bool_t val); LINPHONE_PUBLIC bool_t linphone_proxy_config_quality_reporting_enabled(LinphoneProxyConfig *cfg); + + /** + * Set the SIP address of the collector end-point when using quality reporting. This SIP address + * should be used on server-side to process packets directly then discard packets. Collector address + * should be a non existing account and should not received any packets. + * @param[in] cfg #LinphoneProxyConfig object + * @param[in] collector SIP address of the collector end-point. + */ LINPHONE_PUBLIC void linphone_proxy_config_set_quality_reporting_collector(LinphoneProxyConfig *cfg, const char *collector); -LINPHONE_PUBLIC const char *linphone_proxy_config_get_quality_reporting_collector(const LinphoneProxyConfig *obj); + + /** + * Get the SIP address of the collector end-point when using quality reporting. This SIP address + * should be used on server-side to process packets directly then discard packets. Collector address + * should be a non existing account and should not received any packets. + * @param[in] cfg #LinphoneProxyConfig object + * @return The SIP address of the collector end-point. + */ +LINPHONE_PUBLIC const char *linphone_proxy_config_get_quality_reporting_collector(const LinphoneProxyConfig *cfg); + +/** + * Set the interval between 2 interval reports sending when using quality reporting. If call exceed interval size, an + * interval report will be sent to the collector. On call termination, a session report will be sent + * for the remaining period. + * @param[in] cfg #LinphoneProxyConfig object + * @param[in] interval The interval in seconds. + */ +void linphone_proxy_config_set_quality_reporting_interval(LinphoneProxyConfig *cfg, uint8_t interval); + +/** + * Get the interval between interval reports when using quality reporting. + * @param[in] cfg #LinphoneProxyConfig object + * @return The interval in seconds. + */ + +int linphone_proxy_config_get_quality_reporting_interval(LinphoneProxyConfig *cfg); /** * Get the registration state of the given proxy config. diff --git a/coreapi/private.h b/coreapi/private.h index ed9e78878..5270f5440 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -437,6 +437,7 @@ struct _LinphoneProxyConfig bool_t avpf_enabled; bool_t pad; uint8_t avpf_rr_interval; + uint8_t quality_reporting_interval; void* user_data; time_t deletion_date; LinphonePrivacyMask privacy; diff --git a/coreapi/proxy.c b/coreapi/proxy.c index a72e3523a..cedb97b29 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -108,8 +108,9 @@ static void linphone_proxy_config_init(LinphoneCore* lc, LinphoneProxyConfig *ob obj->reg_identity = identity ? ms_strdup(identity) : NULL; obj->reg_proxy = proxy ? ms_strdup(proxy) : NULL; obj->reg_route = route ? ms_strdup(route) : NULL; - obj->quality_reporting_collector = quality_reporting_collector ? ms_strdup(quality_reporting_collector) : NULL; obj->quality_reporting_enabled = lc ? lp_config_get_default_int(lc->config, "proxy", "quality_reporting_enabled", 0) : 0; + obj->quality_reporting_collector = quality_reporting_collector ? ms_strdup(quality_reporting_collector) : NULL; + obj->quality_reporting_interval = lc ? lp_config_get_default_int(lc->config, "proxy", "quality_reporting_interval", 0) : 0; obj->contact_params = contact_params ? ms_strdup(contact_params) : NULL; obj->contact_uri_params = contact_uri_params ? ms_strdup(contact_uri_params) : NULL; obj->avpf_enabled = lc ? lp_config_get_default_int(lc->config, "proxy", "avpf", 0) : 0; @@ -489,6 +490,14 @@ bool_t linphone_proxy_config_quality_reporting_enabled(LinphoneProxyConfig *cfg) return cfg->quality_reporting_enabled && cfg->quality_reporting_collector != NULL; } +void linphone_proxy_config_set_quality_reporting_interval(LinphoneProxyConfig *cfg, uint8_t interval) { + cfg->quality_reporting_interval = interval; +} + +int linphone_proxy_config_get_quality_reporting_interval(LinphoneProxyConfig *cfg) { + return cfg->quality_reporting_interval; +} + void linphone_proxy_config_set_quality_reporting_collector(LinphoneProxyConfig *cfg, const char *collector){ if (collector!=NULL && strlen(collector)>0){ LinphoneAddress *addr=linphone_address_new(collector); @@ -1174,9 +1183,6 @@ void linphone_proxy_config_write_to_config_file(LpConfig *config, LinphoneProxyC if (obj->reg_route!=NULL){ lp_config_set_string(config,key,"reg_route",obj->reg_route); } - if (obj->quality_reporting_collector!=NULL){ - lp_config_set_string(config,key,"quality_reporting_collector",obj->quality_reporting_collector); - } if (obj->reg_identity!=NULL){ lp_config_set_string(config,key,"reg_identity",obj->reg_identity); } @@ -1186,13 +1192,17 @@ void linphone_proxy_config_write_to_config_file(LpConfig *config, LinphoneProxyC if (obj->contact_uri_params!=NULL){ lp_config_set_string(config,key,"contact_uri_parameters",obj->contact_uri_params); } + if (obj->quality_reporting_collector!=NULL){ + lp_config_set_string(config,key,"quality_reporting_collector",obj->quality_reporting_collector); + } + lp_config_set_int(config,key,"quality_reporting_enabled",obj->quality_reporting_enabled); + lp_config_set_int(config,key,"quality_reporting_interval",obj->quality_reporting_interval); lp_config_set_int(config,key,"reg_expires",obj->expires); lp_config_set_int(config,key,"reg_sendregister",obj->reg_sendregister); lp_config_set_int(config,key,"publish",obj->publish); lp_config_set_int(config, key, "avpf", obj->avpf_enabled); lp_config_set_int(config, key, "avpf_rr_interval", obj->avpf_rr_interval); lp_config_set_int(config,key,"dial_escape_plus",obj->dial_escape_plus); - lp_config_set_int(config,key,"quality_reporting_enabled",obj->quality_reporting_enabled); lp_config_set_string(config,key,"dial_prefix",obj->dial_prefix); lp_config_set_int(config,key,"privacy",obj->privacy); } @@ -1224,9 +1234,10 @@ LinphoneProxyConfig *linphone_proxy_config_new_from_config_file(LpConfig *config tmp=lp_config_get_string(config,key,"reg_route",NULL); if (tmp!=NULL) linphone_proxy_config_set_route(cfg,tmp); + linphone_proxy_config_enable_quality_reporting(cfg,lp_config_get_int(config,key,"quality_reporting_enabled",0)); tmp=lp_config_get_string(config,key,"quality_reporting_collector",NULL); if (tmp!=NULL) linphone_proxy_config_set_quality_reporting_collector(cfg,tmp); - linphone_proxy_config_enable_quality_reporting(cfg,lp_config_get_int(config,key,"quality_reporting_enabled",0)); + linphone_proxy_config_set_quality_reporting_interval(cfg, lp_config_get_int(config, key, "quality_reporting_interval", 5)); linphone_proxy_config_set_contact_parameters(cfg,lp_config_get_string(config,key,"contact_parameters",NULL)); diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c index c9c5e7fd7..4d8a68b23 100644 --- a/coreapi/quality_reporting.c +++ b/coreapi/quality_reporting.c @@ -33,8 +33,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *************************************************************************** For codecs that are able to change sample rates, the lowest and highest sample rates MUST be reported (e.g., 8000;16000). moslq == moscq +valgrind video: what happens if doing stop/resume? rlq value: need algo to compute it +3.4 overload avoidance? - The Session report when session terminates, media change (codec change or a session fork), session terminates due to no media packets being received - The Interval report SHOULD be used for periodic or interval reporting @@ -282,7 +284,7 @@ static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * off ms_free(moscq_str); } -static void send_report(const LinphoneCall* call, reporting_session_report_t * report) { +static void send_report(const LinphoneCall* call, reporting_session_report_t * report, const char * report_event) { LinphoneContent content = {0}; LinphoneAddress *addr; int expires = -1; @@ -295,7 +297,15 @@ static void send_report(const LinphoneCall* call, reporting_session_report_t * r if (report->info.local_addr.ip == NULL || strlen(report->info.local_addr.ip) == 0 || report->info.remote_addr.ip == NULL || strlen(report->info.remote_addr.ip) == 0) { ms_warning("The call was hang up too early (duration: %d sec) and IP could " - "not be retrieved so dropping this report", linphone_call_get_duration(call)); + "not be retrieved so dropping this report" + , linphone_call_get_duration(call)); + return; + } + + /*do not send report if the previous one was sent less than 30seconds ago*/ + if (ms_time(NULL) - report->last_report_date < 30){ + ms_warning("Already sent a report %ld sec ago. Cancel sending this report" + , ms_time(NULL) - report->last_report_date); return; } @@ -309,7 +319,7 @@ static void send_report(const LinphoneCall* call, reporting_session_report_t * r content.type = ms_strdup("application"); content.subtype = ms_strdup("vq-rtcpxr"); - append_to_buffer(&buffer, &size, &offset, "VQSessionReport: CallTerm\r\n"); + append_to_buffer(&buffer, &size, &offset, "%s\r\n", report_event); append_to_buffer(&buffer, &size, &offset, "CallID: %s\r\n", report->info.call_id); append_to_buffer(&buffer, &size, &offset, "LocalID: %s\r\n", report->info.local_id); append_to_buffer(&buffer, &size, &offset, "RemoteID: %s\r\n", report->info.remote_id); @@ -339,6 +349,8 @@ static void send_report(const LinphoneCall* call, reporting_session_report_t * r reset_avg_metrics(report); linphone_content_uninit(&content); + + report->last_report_date = ms_time(NULL); } static const SalStreamDescription * get_media_stream_for_desc(const SalMediaDescription * smd, SalStreamType sal_stream_type) { @@ -533,20 +545,39 @@ void linphone_reporting_on_rtcp_received(LinphoneCall *call, int stats_type) { } } } + + /* check if we should send an interval report */ + if (ms_time(NULL) - report->last_report_date > linphone_proxy_config_get_quality_reporting_interval(call->dest_proxy)){ + linphone_reporting_publish_interval_report(call); + } } -void linphone_reporting_publish_on_call_term(LinphoneCall* call) { +void linphone_reporting_publish_session_report(LinphoneCall* call) { + if (! is_reporting_enabled(call)) + return; + + if (call->log->reports[LINPHONE_CALL_STATS_AUDIO] != NULL) { + send_report(call, call->log->reports[LINPHONE_CALL_STATS_AUDIO], "VQSessionReport: CallTerm"); + } + + if (call->log->reports[LINPHONE_CALL_STATS_VIDEO] != NULL + && linphone_call_params_video_enabled(linphone_call_get_current_params(call))) { + send_report(call, call->log->reports[LINPHONE_CALL_STATS_VIDEO], "VQSessionReport: CallTerm"); + } +} + +void linphone_reporting_publish_interval_report(LinphoneCall* call) { if (! is_reporting_enabled(call)) return; if (call->log->reports[LINPHONE_CALL_STATS_AUDIO] != NULL) { - send_report(call, call->log->reports[LINPHONE_CALL_STATS_AUDIO]); + send_report(call, call->log->reports[LINPHONE_CALL_STATS_AUDIO], "VQIntervalReport"); } if (call->log->reports[LINPHONE_CALL_STATS_VIDEO] != NULL && linphone_call_params_video_enabled(linphone_call_get_current_params(call))) { - send_report(call, call->log->reports[LINPHONE_CALL_STATS_VIDEO]); + send_report(call, call->log->reports[LINPHONE_CALL_STATS_VIDEO], "VQIntervalReport"); } } diff --git a/coreapi/quality_reporting.h b/coreapi/quality_reporting.h index 44704c32a..00fb07215 100644 --- a/coreapi/quality_reporting.h +++ b/coreapi/quality_reporting.h @@ -129,6 +129,9 @@ typedef struct reporting_session_report { reporting_content_metrics_t remote_metrics; // optional char * dialog_id; // optional + + // for internal processing + time_t last_report_date; } reporting_session_report_t; reporting_session_report_t * linphone_reporting_new(); @@ -154,11 +157,19 @@ void linphone_reporting_update_media_info(LinphoneCall * call, int stats_type); void linphone_reporting_update_ip(LinphoneCall * call); /** - * Publish the report on the call end. + * Publish a session report. This function should be called when session terminates, + * media change (codec change or session fork), session terminates due to no media packets being received. * @param call #LinphoneCall object to consider * */ -void linphone_reporting_publish_on_call_term(LinphoneCall* call); +void linphone_reporting_publish_session_report(LinphoneCall* call); + +/** + * Publish an interval report. This function should be used for periodic interval + * @param call #LinphoneCall object to consider + * + */ +void linphone_reporting_publish_interval_report(LinphoneCall* call); /** * Update publish report data with fresh RTCP stats, if needed. From 25186cc0ae03b72c202f75d31033ca89a69366c2 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Thu, 5 Jun 2014 15:25:05 +0200 Subject: [PATCH 77/94] Quality reporting: disabled interval report per default and add minimal value when set (120 seconds minimum) --- coreapi/linphonecore.h | 6 +++--- coreapi/proxy.c | 4 ++-- coreapi/quality_reporting.c | 20 +++++++------------- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index c3ebf9aaf..b70e90600 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -869,16 +869,16 @@ LINPHONE_PUBLIC const char *linphone_proxy_config_get_quality_reporting_collecto /** * Set the interval between 2 interval reports sending when using quality reporting. If call exceed interval size, an * interval report will be sent to the collector. On call termination, a session report will be sent - * for the remaining period. + * for the remaining period. Value must be 0 (disabled) or greater than 120sec to avoid overloading. * @param[in] cfg #LinphoneProxyConfig object - * @param[in] interval The interval in seconds. + * @param[in] interval The interval in seconds, 0 means interval reports are disabled. */ void linphone_proxy_config_set_quality_reporting_interval(LinphoneProxyConfig *cfg, uint8_t interval); /** * Get the interval between interval reports when using quality reporting. * @param[in] cfg #LinphoneProxyConfig object - * @return The interval in seconds. + * @return The interval in seconds, 0 means interval reports are disabled. */ int linphone_proxy_config_get_quality_reporting_interval(LinphoneProxyConfig *cfg); diff --git a/coreapi/proxy.c b/coreapi/proxy.c index cedb97b29..8faaa95cf 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -491,7 +491,7 @@ bool_t linphone_proxy_config_quality_reporting_enabled(LinphoneProxyConfig *cfg) } void linphone_proxy_config_set_quality_reporting_interval(LinphoneProxyConfig *cfg, uint8_t interval) { - cfg->quality_reporting_interval = interval; + cfg->quality_reporting_interval = interval ? MAX(interval, 120) : 0; } int linphone_proxy_config_get_quality_reporting_interval(LinphoneProxyConfig *cfg) { @@ -1237,7 +1237,7 @@ LinphoneProxyConfig *linphone_proxy_config_new_from_config_file(LpConfig *config linphone_proxy_config_enable_quality_reporting(cfg,lp_config_get_int(config,key,"quality_reporting_enabled",0)); tmp=lp_config_get_string(config,key,"quality_reporting_collector",NULL); if (tmp!=NULL) linphone_proxy_config_set_quality_reporting_collector(cfg,tmp); - linphone_proxy_config_set_quality_reporting_interval(cfg, lp_config_get_int(config, key, "quality_reporting_interval", 5)); + linphone_proxy_config_set_quality_reporting_interval(cfg, lp_config_get_int(config, key, "quality_reporting_interval", 0)); linphone_proxy_config_set_contact_parameters(cfg,lp_config_get_string(config,key,"contact_parameters",NULL)); diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c index 4d8a68b23..251f82b38 100644 --- a/coreapi/quality_reporting.c +++ b/coreapi/quality_reporting.c @@ -100,9 +100,9 @@ static void append_to_buffer(char **buff, size_t *buff_size, size_t *offset, con va_end(args); } -static void reset_avg_metrics(reporting_session_report_t * rm){ +static void reset_avg_metrics(reporting_session_report_t * report){ int i; - reporting_content_metrics_t * metrics[2] = {&rm->local_metrics, &rm->remote_metrics}; + reporting_content_metrics_t * metrics[2] = {&report->local_metrics, &report->remote_metrics}; for (i = 0; i < 2; i++) { metrics[i]->rtcp_xr_count = 0; @@ -110,8 +110,10 @@ static void reset_avg_metrics(reporting_session_report_t * rm){ metrics[i]->jitter_buffer.max = 0; metrics[i]->delay.round_trip_delay = 0; + metrics[i]->delay.symm_one_way_delay = 0; } + report->last_report_date = ms_time(NULL); } #define APPEND_IF_NOT_NULL_STR(buffer, size, offset, fmt, arg) if (arg != NULL) append_to_buffer(buffer, size, offset, fmt, arg) @@ -302,13 +304,6 @@ static void send_report(const LinphoneCall* call, reporting_session_report_t * r return; } - /*do not send report if the previous one was sent less than 30seconds ago*/ - if (ms_time(NULL) - report->last_report_date < 30){ - ms_warning("Already sent a report %ld sec ago. Cancel sending this report" - , ms_time(NULL) - report->last_report_date); - return; - } - addr = linphone_address_new(linphone_proxy_config_get_quality_reporting_collector(call->dest_proxy)); if (addr == NULL) { ms_warning("Asked to submit reporting statistics but no collector address found"); @@ -349,8 +344,6 @@ static void send_report(const LinphoneCall* call, reporting_session_report_t * r reset_avg_metrics(report); linphone_content_uninit(&content); - - report->last_report_date = ms_time(NULL); } static const SalStreamDescription * get_media_stream_for_desc(const SalMediaDescription * smd, SalStreamType sal_stream_type) { @@ -495,6 +488,8 @@ void linphone_reporting_on_rtcp_received(LinphoneCall *call, int stats_type) { LinphoneCallStats stats = call->stats[stats_type]; mblk_t *block = NULL; + int report_interval = linphone_proxy_config_get_quality_reporting_interval(call->dest_proxy); + if (! is_reporting_enabled(call)) return; @@ -547,7 +542,7 @@ void linphone_reporting_on_rtcp_received(LinphoneCall *call, int stats_type) { } /* check if we should send an interval report */ - if (ms_time(NULL) - report->last_report_date > linphone_proxy_config_get_quality_reporting_interval(call->dest_proxy)){ + if (report_interval>0 && ms_time(NULL)-report->last_report_date>report_interval){ linphone_reporting_publish_interval_report(call); } } @@ -570,7 +565,6 @@ void linphone_reporting_publish_interval_report(LinphoneCall* call) { if (! is_reporting_enabled(call)) return; - if (call->log->reports[LINPHONE_CALL_STATS_AUDIO] != NULL) { send_report(call, call->log->reports[LINPHONE_CALL_STATS_AUDIO], "VQIntervalReport"); } From ff6cbfc0f37c3eae1acb7d65c1e4da6625e23b84 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Thu, 5 Jun 2014 16:56:54 +0200 Subject: [PATCH 78/94] Display available tests if the given one is not existing --- tester/liblinphone_tester.c | 8 +++----- tester/liblinphone_tester.h | 1 + tester/tester.c | 8 ++++++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/tester/liblinphone_tester.c b/tester/liblinphone_tester.c index 7b5043680..c5d017566 100644 --- a/tester/liblinphone_tester.c +++ b/tester/liblinphone_tester.c @@ -197,10 +197,7 @@ int main (int argc, char *argv[]) } else if (strcmp(argv[i],"--list-tests")==0){ CHECK_ARG("--list-tests", ++i, argc); suite_name = argv[i]; - for(j=0;j Date: Thu, 5 Jun 2014 16:59:21 +0200 Subject: [PATCH 79/94] fill media information when sending interval report too and added unit test for it (quality_reporting_interval_report) --- coreapi/linphonecore.h | 2 +- coreapi/proxy.c | 6 ++++-- coreapi/quality_reporting.c | 30 +++++++++++++++++------------- coreapi/quality_reporting.h | 8 +++++--- tester/call_tester.c | 29 +++++++++++++++++++++++++---- 5 files changed, 52 insertions(+), 23 deletions(-) diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index b70e90600..bb35fc98c 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -869,7 +869,7 @@ LINPHONE_PUBLIC const char *linphone_proxy_config_get_quality_reporting_collecto /** * Set the interval between 2 interval reports sending when using quality reporting. If call exceed interval size, an * interval report will be sent to the collector. On call termination, a session report will be sent - * for the remaining period. Value must be 0 (disabled) or greater than 120sec to avoid overloading. + * for the remaining period. Value must be 0 (disabled) or positive. * @param[in] cfg #LinphoneProxyConfig object * @param[in] interval The interval in seconds, 0 means interval reports are disabled. */ diff --git a/coreapi/proxy.c b/coreapi/proxy.c index 8faaa95cf..3772bc744 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -491,7 +491,7 @@ bool_t linphone_proxy_config_quality_reporting_enabled(LinphoneProxyConfig *cfg) } void linphone_proxy_config_set_quality_reporting_interval(LinphoneProxyConfig *cfg, uint8_t interval) { - cfg->quality_reporting_interval = interval ? MAX(interval, 120) : 0; + cfg->quality_reporting_interval = interval; } int linphone_proxy_config_get_quality_reporting_interval(LinphoneProxyConfig *cfg) { @@ -1216,6 +1216,7 @@ LinphoneProxyConfig *linphone_proxy_config_new_from_config_file(LpConfig *config const char *proxy; LinphoneProxyConfig *cfg; char key[50]; + int interval; sprintf(key,"proxy_%i",index); @@ -1237,7 +1238,8 @@ LinphoneProxyConfig *linphone_proxy_config_new_from_config_file(LpConfig *config linphone_proxy_config_enable_quality_reporting(cfg,lp_config_get_int(config,key,"quality_reporting_enabled",0)); tmp=lp_config_get_string(config,key,"quality_reporting_collector",NULL); if (tmp!=NULL) linphone_proxy_config_set_quality_reporting_collector(cfg,tmp); - linphone_proxy_config_set_quality_reporting_interval(cfg, lp_config_get_int(config, key, "quality_reporting_interval", 0)); + interval=lp_config_get_int(config, key, "quality_reporting_interval", 0); + linphone_proxy_config_set_quality_reporting_interval(cfg, interval? MAX(interval, 120) : 0); linphone_proxy_config_set_contact_parameters(cfg,lp_config_get_string(config,key,"contact_parameters",NULL)); diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c index 251f82b38..81241efd1 100644 --- a/coreapi/quality_reporting.c +++ b/coreapi/quality_reporting.c @@ -35,6 +35,8 @@ For codecs that are able to change sample rates, the lowest and highest sample r moslq == moscq valgrind video: what happens if doing stop/resume? +one single report <- merge audio/video? +unit test interval report rlq value: need algo to compute it 3.4 overload avoidance? @@ -111,7 +113,7 @@ static void reset_avg_metrics(reporting_session_report_t * report){ metrics[i]->delay.round_trip_delay = 0; - metrics[i]->delay.symm_one_way_delay = 0; + /*metrics[i]->delay.symm_one_way_delay = 0;*/ } report->last_report_date = ms_time(NULL); } @@ -147,15 +149,15 @@ static uint8_t are_metrics_filled(const reporting_content_metrics_t rm) { IF_NUM_IN_RANGE(rm.jitter_buffer.abs_max, 0, 65535, ret|=METRICS_JITTER_BUFFER); IF_NUM_IN_RANGE(rm.delay.end_system_delay, 0, 65535, ret|=METRICS_DELAY); - IF_NUM_IN_RANGE(rm.delay.symm_one_way_delay, 0, 65535, ret|=METRICS_DELAY); + /*IF_NUM_IN_RANGE(rm.delay.symm_one_way_delay, 0, 65535, ret|=METRICS_DELAY);*/ IF_NUM_IN_RANGE(rm.delay.interarrival_jitter, 0, 65535, ret|=METRICS_DELAY); IF_NUM_IN_RANGE(rm.delay.mean_abs_jitter, 0, 65535, ret|=METRICS_DELAY); if (rm.signal.level != 127) ret|=METRICS_SIGNAL; if (rm.signal.noise_level != 127) ret|=METRICS_SIGNAL; - if (rm.adaptive_algorithm.input!=NULL) ret|=METRICS_ADAPTIVE_ALGORITHM; - if (rm.adaptive_algorithm.output!=NULL) ret|=METRICS_ADAPTIVE_ALGORITHM; + if (rm.qos_analyzer.input!=NULL) ret|=METRICS_ADAPTIVE_ALGORITHM; + if (rm.qos_analyzer.output!=NULL) ret|=METRICS_ADAPTIVE_ALGORITHM; if (rm.rtcp_xr_count>0){ IF_NUM_IN_RANGE(rm.jitter_buffer.nominal/rm.rtcp_xr_count, 0, 65535, ret|=METRICS_JITTER_BUFFER); @@ -236,7 +238,7 @@ static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * off } APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " ESD=%d", rm.delay.end_system_delay, 0, 65535); /*APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " OWD=%d", rm.delay.one_way_delay, 0, 65535);*/ - APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " SOWD=%d", rm.delay.symm_one_way_delay, 0, 65535); + /*APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " SOWD=%d", rm.delay.symm_one_way_delay, 0, 65535);*/ APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " IAJ=%d", rm.delay.interarrival_jitter, 0, 65535); APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " MAJ=%d", rm.delay.mean_abs_jitter, 0, 65535); } @@ -271,8 +273,8 @@ static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * off if ((available_metrics & METRICS_ADAPTIVE_ALGORITHM) != 0){ append_to_buffer(buffer, size, offset, "\r\nAdaptiveAlg:"); - APPEND_IF_NOT_NULL_STR(buffer, size, offset, " IN=%s", rm.adaptive_algorithm.input); - APPEND_IF_NOT_NULL_STR(buffer, size, offset, " OUT=%s", rm.adaptive_algorithm.output); + APPEND_IF_NOT_NULL_STR(buffer, size, offset, " IN=%s", rm.qos_analyzer.input); + APPEND_IF_NOT_NULL_STR(buffer, size, offset, " OUT=%s", rm.qos_analyzer.output); } append_to_buffer(buffer, size, offset, "\r\n"); @@ -395,11 +397,11 @@ static bool_t is_reporting_enabled(const LinphoneCall * call) { static void qos_analyser_on_action_suggested(void *user_data, const char * input, const char * output){ reporting_content_metrics_t *metrics = (reporting_content_metrics_t*) user_data; char * newstr = NULL; - newstr = ms_strdup_printf("%s%s;", metrics->adaptive_algorithm.input?metrics->adaptive_algorithm.input:"", input); - STR_REASSIGN(metrics->adaptive_algorithm.input, newstr) + newstr = ms_strdup_printf("%s%s;", metrics->qos_analyzer.input?metrics->qos_analyzer.input:"", input); + STR_REASSIGN(metrics->qos_analyzer.input, newstr) - newstr = ms_strdup_printf("%s%s;", metrics->adaptive_algorithm.output?metrics->adaptive_algorithm.output:"", output); - STR_REASSIGN(metrics->adaptive_algorithm.output, newstr) + newstr = ms_strdup_printf("%s%s;", metrics->qos_analyzer.output?metrics->qos_analyzer.output:"", output); + STR_REASSIGN(metrics->qos_analyzer.output, newstr) } void linphone_reporting_update_ip(LinphoneCall * call) { @@ -566,11 +568,13 @@ void linphone_reporting_publish_interval_report(LinphoneCall* call) { return; if (call->log->reports[LINPHONE_CALL_STATS_AUDIO] != NULL) { + linphone_reporting_update_media_info(call, LINPHONE_CALL_STATS_AUDIO); send_report(call, call->log->reports[LINPHONE_CALL_STATS_AUDIO], "VQIntervalReport"); } if (call->log->reports[LINPHONE_CALL_STATS_VIDEO] != NULL && linphone_call_params_video_enabled(linphone_call_get_current_params(call))) { + linphone_reporting_update_media_info(call, LINPHONE_CALL_STATS_VIDEO); send_report(call, call->log->reports[LINPHONE_CALL_STATS_VIDEO], "VQIntervalReport"); } } @@ -625,8 +629,8 @@ void linphone_reporting_destroy(reporting_session_report_t * report) { if (report->local_metrics.session_description.payload_desc != NULL) ms_free(report->local_metrics.session_description.payload_desc); if (report->remote_metrics.session_description.fmtp != NULL) ms_free(report->remote_metrics.session_description.fmtp); if (report->remote_metrics.session_description.payload_desc != NULL) ms_free(report->remote_metrics.session_description.payload_desc); - if (report->local_metrics.adaptive_algorithm.input != NULL) ms_free(report->local_metrics.adaptive_algorithm.input); - if (report->local_metrics.adaptive_algorithm.output != NULL) ms_free(report->local_metrics.adaptive_algorithm.output); + if (report->local_metrics.qos_analyzer.input != NULL) ms_free(report->local_metrics.qos_analyzer.input); + if (report->local_metrics.qos_analyzer.output != NULL) ms_free(report->local_metrics.qos_analyzer.output); ms_free(report); } diff --git a/coreapi/quality_reporting.h b/coreapi/quality_reporting.h index 00fb07215..2dc888a89 100644 --- a/coreapi/quality_reporting.h +++ b/coreapi/quality_reporting.h @@ -75,7 +75,7 @@ typedef struct reporting_content_metrics { struct { int round_trip_delay; // no - vary int end_system_delay; // no - not implemented yet - int symm_one_way_delay; // no - vary (depends on round_trip_delay) + not implemented (depends on end_system_delay) + int symm_one_way_delay; // no - not implemented (depends on end_system_delay) int interarrival_jitter; // no - not implemented yet int mean_abs_jitter; // to check } delay; @@ -94,11 +94,13 @@ typedef struct reporting_content_metrics { float moscq; // no - vary or avg - voip metrics - in [0..4.9] } quality_estimates; - // adaptive algorithm - custom extension + // Quality of Service analyzer - custom extension + /* This should allow us to analysis bad network conditions and quality adaptation + on server side*/ struct { char* input; char* output; - } adaptive_algorithm; + } qos_analyzer; // for internal processing uint8_t rtcp_xr_count; // number of RTCP XR packets received since last report, used to compute average of instantaneous parameters as stated in the RFC 6035 (4.5) diff --git a/tester/call_tester.c b/tester/call_tester.c index f1b94bbd2..7e2617ca3 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -2202,7 +2202,6 @@ static void quality_reporting_not_sent_if_call_not_started() { linphone_core_manager_destroy(pauline); } static void quality_reporting_at_call_termination() { - // int return_code = -1; LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); LinphoneCall* call_marie = NULL; @@ -2228,6 +2227,27 @@ static void quality_reporting_at_call_termination() { linphone_core_manager_destroy(pauline); } +static void quality_reporting_interval_report() { + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + LinphoneCall* call_marie = NULL; + LinphoneCall* call_pauline = NULL; + + create_call_for_quality_reporting_tests(marie, pauline, &call_marie, &call_pauline); + linphone_proxy_config_set_quality_reporting_interval(call_marie->dest_proxy, 3); + + CU_ASSERT_PTR_NOT_NULL(linphone_core_get_current_call(marie->lc)); + CU_ASSERT_PTR_NOT_NULL(linphone_core_get_current_call(pauline->lc)); + + // PUBLISH submission to the collector should be ok + CU_ASSERT_TRUE(wait_for_until(marie->lc,pauline->lc,&marie->stat.number_of_LinphonePublishProgress,3,15000)); + CU_ASSERT_TRUE(wait_for_until(marie->lc,pauline->lc,&marie->stat.number_of_LinphonePublishOk,3,15000)); + + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + + #ifdef VIDEO_ENABLED /*this is call forking with early media managed at client side (not by flexisip server)*/ static void multiple_early_media(void) { @@ -2374,9 +2394,10 @@ test_t call_tests[] = { { "Call established with rejected incoming RE-INVITE", call_established_with_rejected_incoming_reinvite }, { "Call established with rejected RE-INVITE in error", call_established_with_rejected_reinvite_with_error}, { "Call redirected by callee", call_redirect}, - { "Call quality reporting not used if no config", quality_reporting_not_used_without_config}, - { "Call quality reporting not sent if call did not start", quality_reporting_not_sent_if_call_not_started}, - { "Call quality reporting sent if call ended normally", quality_reporting_at_call_termination}, + { "Quality reporting not used if no config", quality_reporting_not_used_without_config}, + { "Quality reporting session report not sent if call did not start", quality_reporting_not_sent_if_call_not_started}, + { "Quality reporting session report sent if call ended normally", quality_reporting_at_call_termination}, + { "Quality reporting interval report if interval is configured", quality_reporting_interval_report}, { "Call with specified codec bitrate", call_with_specified_codec_bitrate} }; From 4094aec87ab5a5915dc7d985d7670d360709eff2 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Thu, 5 Jun 2014 17:38:15 +0200 Subject: [PATCH 80/94] removed minor warnings generated by doxygen due to outdated functions prototype --- coreapi/chat.c | 2 +- coreapi/event.h | 9 ++++----- coreapi/linphone_tunnel.h | 12 ++++++------ coreapi/linphonecall.c | 2 +- coreapi/linphonecore.c | 8 +++++--- coreapi/linphonecore.h | 8 ++++---- coreapi/linphonepresence.h | 2 +- coreapi/proxy.c | 2 +- coreapi/quality_reporting.c | 1 - 9 files changed, 23 insertions(+), 23 deletions(-) diff --git a/coreapi/chat.c b/coreapi/chat.c index 2ea196b47..c20cfef9e 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -247,7 +247,7 @@ void linphone_chat_room_message_received(LinphoneChatRoom *cr, LinphoneCore *lc, /** * Retrieve an existing chat room whose peer is the supplied address, if exists. * @param lc the linphone core - * @param add a linphone address. + * @param addr a linphone address. * @returns the matching chatroom, or NULL if no such chatroom exists. **/ LinphoneChatRoom *linphone_core_get_chat_room(LinphoneCore *lc, const LinphoneAddress *addr){ diff --git a/coreapi/event.h b/coreapi/event.h index 2b0e2bf0d..4b3e26bd9 100644 --- a/coreapi/event.h +++ b/coreapi/event.h @@ -97,12 +97,12 @@ typedef void (*LinphoneCoreNotifyReceivedCb)(LinphoneCore *lc, LinphoneEvent *le /** * Callback prototype for notifying the application about changes of subscription states, including arrival of new subscriptions. -**/ +**/ typedef void (*LinphoneCoreSubscriptionStateChangedCb)(LinphoneCore *lc, LinphoneEvent *lev, LinphoneSubscriptionState state); /** * Callback prototype for notifying the application about changes of publish states. -**/ +**/ typedef void (*LinphoneCorePublishStateChangedCb)(LinphoneCore *lc, LinphoneEvent *lev, LinphonePublishState state); /** @@ -125,7 +125,6 @@ LINPHONE_PUBLIC LinphoneEvent *linphone_core_subscribe(LinphoneCore *lc, const L * @param resource the destination resource * @param event the event name * @param expires the whished duration of the subscription - * @param body an optional body, may be NULL. * @return a LinphoneEvent holding the context of the created subcription. **/ LINPHONE_PUBLIC LinphoneEvent *linphone_core_create_subscribe(LinphoneCore *lc, const LinphoneAddress *resource, const char *event, int expires); @@ -258,7 +257,7 @@ LINPHONE_PUBLIC const char *linphone_event_get_custom_header(LinphoneEvent *ev, /** * Terminate an incoming or outgoing subscription that was previously acccepted, or a previous publication. * This function does not unref the object. The core will unref() if it does not need this object anymore. - * + * * For subscribed event, when the subscription is terminated normally or because of an error, the core will unref. * For published events, no unref is performed. This is because it is allowed to re-publish an expired publish, as well as retry it in case of error. **/ @@ -270,7 +269,7 @@ LINPHONE_PUBLIC void linphone_event_terminate(LinphoneEvent *lev); * By default LinphoneEvents created by the core are owned by the core only. * An application that wishes to retain a reference to it must call linphone_event_ref(). * When this reference is no longer needed, linphone_event_unref() must be called. - * + * **/ LINPHONE_PUBLIC LinphoneEvent *linphone_event_ref(LinphoneEvent *lev); diff --git a/coreapi/linphone_tunnel.h b/coreapi/linphone_tunnel.h index cff8fc532..9b33e32f8 100644 --- a/coreapi/linphone_tunnel.h +++ b/coreapi/linphone_tunnel.h @@ -22,7 +22,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ - + #ifndef LINPHONETUNNEL_H #define LINPHONETUNNEL_H @@ -86,7 +86,7 @@ LINPHONE_PUBLIC void linphone_tunnel_config_set_port(LinphoneTunnelConfig *tunne LINPHONE_PUBLIC int linphone_tunnel_config_get_port(const LinphoneTunnelConfig *tunnel); /** - * Set the remote port on the tunnel server side used to test udp reachability. + * Set the remote port on the tunnel server side used to test udp reachability. * * @param tunnel configuration object * @param remote_udp_mirror_port remote port on the tunnel server side used to test udp reachability, set to -1 to disable the feature @@ -110,7 +110,7 @@ LINPHONE_PUBLIC void linphone_tunnel_config_set_delay(LinphoneTunnelConfig *tunn /** * Get the udp packet round trip delay in ms for a tunnel configuration. - * + * * @param tunnel configuration object */ LINPHONE_PUBLIC int linphone_tunnel_config_get_delay(const LinphoneTunnelConfig *tunnel); @@ -132,7 +132,7 @@ LINPHONE_PUBLIC void linphone_tunnel_add_server(LinphoneTunnel *tunnel, Linphone /** * Remove tunnel server configuration - * + * * @param tunnel object * @param tunnel_config object */ @@ -208,7 +208,7 @@ LINPHONE_PUBLIC bool_t linphone_tunnel_auto_detect_enabled(LinphoneTunnel *tunne * @param host Http proxy host. * @param port http proxy port. * @param username optional http proxy username if the proxy request authentication. Currently only basic authentication is supported. Use NULL if not needed. - * @param password optional http proxy password. Use NULL if not needed. + * @param passwd optional http proxy password. Use NULL if not needed. **/ LINPHONE_PUBLIC void linphone_tunnel_set_http_proxy(LinphoneTunnel *tunnel, const char *host, int port, const char* username,const char* passwd); @@ -218,7 +218,7 @@ LINPHONE_PUBLIC void linphone_tunnel_set_http_proxy(LinphoneTunnel *tunnel, cons * @param host Http proxy host. * @param port http proxy port. * @param username optional http proxy username if the proxy request authentication. Currently only basic authentication is supported. Use NULL if not needed. - * @param password optional http proxy password. Use NULL if not needed. + * @param passwd optional http proxy password. Use NULL if not needed. **/ LINPHONE_PUBLIC void linphone_tunnel_get_http_proxy(LinphoneTunnel*tunnel,const char **host, int *port, const char **username, const char **passwd); diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 2c6c188b4..7fa8b5fc5 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -1447,7 +1447,7 @@ void _linphone_call_params_copy(LinphoneCallParams *ncp, const LinphoneCallParam * Set requested level of privacy for the call. * \xmlonly javascript \endxmlonly * @param params the call parameters to be modified - * @param LinphonePrivacy to configure privacy + * @param privacy LinphonePrivacy to configure privacy * */ void linphone_call_params_set_privacy(LinphoneCallParams *params, LinphonePrivacyMask privacy) { params->privacy=privacy; diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 0183979a5..5c4e51ddf 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -2037,6 +2037,7 @@ int linphone_core_get_sip_transports(LinphoneCore *lc, LCSipTransports *tr){ * A zero value means that the transport is not activated. * If LC_SIP_TRANSPORT_RANDOM was passed to linphone_core_set_sip_transports(), the random port choosed by the system is returned. * @ingroup network_parameters + * @param lc the LinphoneCore * @param tr a LCSipTransports structure. **/ void linphone_core_get_sip_transports_used(LinphoneCore *lc, LCSipTransports *tr){ @@ -2914,7 +2915,7 @@ LinphoneCall * linphone_core_invite_address_with_params(LinphoneCore *lc, const * * It is possible to follow the progress of the transfer provided that transferee sends notification about it. * In this case, the transfer_state_changed callback of the #LinphoneCoreVTable is invoked to notify of the state of the new call at the other party. - * The notified states are #LinphoneCallOutgoingInit , #LinphoneCallOutgoingProgress, #LinphoneCallOutgoingRinging and #LinphoneCallOutgoingConnected. + * The notified states are #LinphoneCallOutgoingInit , #LinphoneCallOutgoingProgress, #LinphoneCallOutgoingRinging and #LinphoneCallConnected. **/ int linphone_core_transfer_call(LinphoneCore *lc, LinphoneCall *call, const char *url) { @@ -3147,7 +3148,7 @@ int linphone_core_update_call(LinphoneCore *lc, LinphoneCall *call, const Linpho #if defined(VIDEO_ENABLED) && defined(BUILD_UPNP) bool_t has_video = FALSE; #endif - + switch(call->state){ case LinphoneCallIncomingEarlyMedia: case LinphoneCallIncomingReceived: @@ -3158,7 +3159,7 @@ int linphone_core_update_call(LinphoneCore *lc, LinphoneCall *call, const Linpho ms_error("linphone_core_update_call() is not allowed in [%s] state",linphone_call_state_to_string(call->state)); return -1; } - + if (params!=NULL){ linphone_call_set_state(call,LinphoneCallUpdating,"Updating call"); #if defined(VIDEO_ENABLED) && defined(BUILD_UPNP) @@ -5614,6 +5615,7 @@ void linphone_core_set_rtp_transport_factories(LinphoneCore* lc, LinphoneRtpTran /** * Retrieve RTP statistics regarding current call. + * @param lc the LinphoneCore * @param local RTP statistics computed locally. * @param remote RTP statistics computed by far end (obtained via RTCP feedback). * diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index bb35fc98c..debf48461 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -819,7 +819,7 @@ LINPHONE_PUBLIC void linphone_proxy_config_enable_publish(LinphoneProxyConfig *o /** * Set the publish expiration time in second. * @param obj proxy config - * @param exires in second + * @param expires in second * */ LINPHONE_PUBLIC void linphone_proxy_config_set_publish_expires(LinphoneProxyConfig *obj, int expires); @@ -964,7 +964,7 @@ LINPHONE_PUBLIC void * linphone_proxy_config_get_user_data(LinphoneProxyConfig * /** * Set default privacy policy for all calls routed through this proxy. * @param params to be modified - * @param LinphonePrivacy to configure privacy + * @param privacy LinphonePrivacy to configure privacy * */ LINPHONE_PUBLIC void linphone_proxy_config_set_privacy(LinphoneProxyConfig *params, LinphonePrivacyMask privacy); /** @@ -1581,7 +1581,7 @@ LINPHONE_PUBLIC int linphone_core_accept_call_update(LinphoneCore *lc, LinphoneC /** * @ingroup media_parameters * Get default call parameters reflecting current linphone core configuration - * @param LinphoneCore object + * @param lc LinphoneCore object * @return LinphoneCallParams */ LINPHONE_PUBLIC LinphoneCallParams *linphone_core_create_default_call_parameters(LinphoneCore *lc); @@ -2023,7 +2023,7 @@ LINPHONE_PUBLIC void linphone_core_mute_mic(LinphoneCore *lc, bool_t muted); /** * Get mic state. - * @deprecated Use #linphone_core_is_mic_enabled instead + * @deprecated Use #linphone_core_mic_enabled instead **/ LINPHONE_PUBLIC bool_t linphone_core_is_mic_muted(LinphoneCore *lc); diff --git a/coreapi/linphonepresence.h b/coreapi/linphonepresence.h index 851ccf63e..780ae22b6 100644 --- a/coreapi/linphonepresence.h +++ b/coreapi/linphonepresence.h @@ -426,7 +426,7 @@ LINPHONE_PUBLIC int linphone_presence_model_clear_persons(LinphonePresenceModel * * The created presence service has the basic status 'closed'. */ -LINPHONE_PUBLIC LinphonePresenceService * linphone_presence_service_new(const char *id, LinphonePresenceBasicStatus, const char *contact); +LINPHONE_PUBLIC LinphonePresenceService * linphone_presence_service_new(const char *id, LinphonePresenceBasicStatus basic_status, const char *contact); /** * Gets the id of a presence service. diff --git a/coreapi/proxy.c b/coreapi/proxy.c index 3772bc744..6b23eebdd 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -1029,7 +1029,7 @@ void linphone_proxy_config_set_contact_parameters(LinphoneProxyConfig *obj, cons /** * Set optional contact parameters that will be added to the contact information sent in the registration, inside the URI. * @param obj the proxy config object - * @param contact_params a string contaning the additional parameters in text form, like "myparam=something;myparam2=something_else" + * @param contact_uri_params a string containing the additional parameters in text form, like "myparam=something;myparam2=something_else" * * The main use case for this function is provide the proxy additional information regarding the user agent, like for example unique identifier or apple push id. * As an example, the contact address in the SIP register sent will look like . diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c index 81241efd1..4d1108077 100644 --- a/coreapi/quality_reporting.c +++ b/coreapi/quality_reporting.c @@ -36,7 +36,6 @@ moslq == moscq valgrind video: what happens if doing stop/resume? one single report <- merge audio/video? -unit test interval report rlq value: need algo to compute it 3.4 overload avoidance? From 728e76cdfb0cbfe4a5efbf933188bb170a1a591d Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Thu, 5 Jun 2014 17:50:17 +0200 Subject: [PATCH 81/94] redisplay failed test at the end of all tests --- mediastreamer2 | 2 +- tester/tester.c | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index f7eab3265..3dbbb2367 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit f7eab3265577feb885dba012d34a10f1b9be072b +Subproject commit 3dbbb23674085d116c4b43063f3cf082b08565ae diff --git a/tester/tester.c b/tester/tester.c index 1cc4acebc..dd17c9190 100644 --- a/tester/tester.c +++ b/tester/tester.c @@ -403,6 +403,13 @@ int liblinphone_tester_run_tests(const char *suite_name, const char *test_name) } ret=CU_get_number_of_tests_failed()!=0; + + /* Redisplay list of failed tests on end */ + if (CU_get_number_of_failure_records()){ + CU_basic_show_failures(CU_get_failure_list()); + printf("\n"); + } + CU_cleanup_registry(); return ret; } From fe2e927642fb60086d6f5ca52ca8746d0bc1f5e8 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Thu, 5 Jun 2014 20:52:05 +0200 Subject: [PATCH 82/94] fix memleak --- tester/call_tester.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tester/call_tester.c b/tester/call_tester.c index 7e2617ca3..bede109ed 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -967,6 +967,9 @@ static void video_call_base(LinphoneCoreManager* pauline,LinphoneCoreManager* ma CU_ASSERT_TRUE(call_with_params(pauline,marie,caller_params,callee_params)); marie_call=linphone_core_get_current_call(marie->lc); pauline_call=linphone_core_get_current_call(pauline->lc); + + linphone_call_params_destroy(caller_params); + linphone_call_params_destroy(callee_params); if (marie_call && pauline_call ) { CU_ASSERT_TRUE(linphone_call_log_video_enabled(linphone_call_get_call_log(marie_call))); From 2f8244d39b12297f86fddd314fb302450ba067a8 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Fri, 6 Jun 2014 10:07:25 +0200 Subject: [PATCH 83/94] memory leak hunting --- coreapi/bellesip_sal/sal_impl.c | 5 ++--- coreapi/bellesip_sal/sal_op_call.c | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/coreapi/bellesip_sal/sal_impl.c b/coreapi/bellesip_sal/sal_impl.c index 6896496f7..92017616d 100644 --- a/coreapi/bellesip_sal/sal_impl.c +++ b/coreapi/bellesip_sal/sal_impl.c @@ -342,7 +342,6 @@ static void process_response_event(void *user_ctx, const belle_sip_response_even break; case 401: case 407: - /*belle_sip_transaction_set_application_data(BELLE_SIP_TRANSACTION(client_transaction),NULL);*//*remove op from trans*/ if (op->state == SalOpStateTerminating && strcmp("BYE",belle_sip_request_get_method(request))!=0) { /*only bye are completed*/ belle_sip_message("Op is in state terminating, nothing else to do "); @@ -396,7 +395,7 @@ static void process_transaction_terminated(void *user_ctx, const belle_sip_trans if(client_transaction) trans=BELLE_SIP_TRANSACTION(client_transaction); else - trans=BELLE_SIP_TRANSACTION(server_transaction); + trans=BELLE_SIP_TRANSACTION(server_transaction); op = (SalOp*)belle_sip_transaction_get_application_data(trans); if (op && op->callbacks && op->callbacks->process_transaction_terminated) { @@ -404,7 +403,7 @@ static void process_transaction_terminated(void *user_ctx, const belle_sip_trans } else { ms_message("Unhandled transaction terminated [%p]",trans); } - if (op && client_transaction) sal_op_unref(op); /*because every client transaction ref op*/ + if (op) sal_op_unref(op); /*because every transaction ref op*/ } diff --git a/coreapi/bellesip_sal/sal_op_call.c b/coreapi/bellesip_sal/sal_op_call.c index f8d35e15c..4d61feca9 100644 --- a/coreapi/bellesip_sal/sal_op_call.c +++ b/coreapi/bellesip_sal/sal_op_call.c @@ -427,8 +427,7 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t 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)); belle_sip_object_ref(server_transaction); - belle_sip_transaction_set_application_data(BELLE_SIP_TRANSACTION(server_transaction),op); - sal_op_ref(op); + belle_sip_transaction_set_application_data(BELLE_SIP_TRANSACTION(server_transaction),sal_op_ref(op)); } if (strcmp("INVITE",method)==0) { From fbb56c4c710c7736bcfb2599063a37dd6d36941e Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Fri, 6 Jun 2014 11:24:05 +0200 Subject: [PATCH 84/94] update submodules --- coreapi/quality_reporting.c | 14 +++++++------- mediastreamer2 | 2 +- oRTP | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c index 4d1108077..102f38c9e 100644 --- a/coreapi/quality_reporting.c +++ b/coreapi/quality_reporting.c @@ -393,7 +393,7 @@ static bool_t is_reporting_enabled(const LinphoneCall * call) { return (call->dest_proxy != NULL && linphone_proxy_config_quality_reporting_enabled(call->dest_proxy)); } -static void qos_analyser_on_action_suggested(void *user_data, const char * input, const char * output){ +static void qos_analyzer_on_action_suggested(void *user_data, const char * input, const char * output){ reporting_content_metrics_t *metrics = (reporting_content_metrics_t*) user_data; char * newstr = NULL; newstr = ms_strdup_printf("%s%s;", metrics->qos_analyzer.input?metrics->qos_analyzer.input:"", input); @@ -485,7 +485,7 @@ void linphone_reporting_update_media_info(LinphoneCall * call, int stats_type) { void linphone_reporting_on_rtcp_received(LinphoneCall *call, int stats_type) { reporting_session_report_t * report = call->log->reports[stats_type]; reporting_content_metrics_t * metrics = NULL; - MSQosAnalyser *analyser=NULL; + MSQosAnalyzer *analyzer=NULL; LinphoneCallStats stats = call->stats[stats_type]; mblk_t *block = NULL; @@ -505,11 +505,11 @@ void linphone_reporting_on_rtcp_received(LinphoneCall *call, int stats_type) { block = stats.sent_rtcp; } } - if (call->audiostream->ms.rc){ - analyser=ms_bitrate_controller_get_qos_analyser(call->audiostream->ms.rc); - if (analyser){ - ms_qos_analyser_set_on_action_suggested(analyser, - qos_analyser_on_action_suggested, + if (call->audiostream->ms.use_rc&&call->audiostream->ms.rc){ + analyzer=ms_bitrate_controller_get_qos_analyzer(call->audiostream->ms.rc); + if (analyzer){ + ms_qos_analyzer_set_on_action_suggested(analyzer, + qos_analyzer_on_action_suggested, &report->local_metrics); } } diff --git a/mediastreamer2 b/mediastreamer2 index 3dbbb2367..c3529c41b 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 3dbbb23674085d116c4b43063f3cf082b08565ae +Subproject commit c3529c41b46ae5e099566462b3fb7f055441ddfb diff --git a/oRTP b/oRTP index ec4c194c6..c889f49b6 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit ec4c194c62a9657f075d07dc2495f98914ee0b8b +Subproject commit c889f49b6c6367b2c3ccb44fa74a70e73dd1b575 From 721d35d5f02f208a818d1acc24a79fef3999b166 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 6 Jun 2014 15:24:54 +0200 Subject: [PATCH 85/94] Update oRTP submodule. --- oRTP | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oRTP b/oRTP index c889f49b6..f0fa4ba94 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit c889f49b6c6367b2c3ccb44fa74a70e73dd1b575 +Subproject commit f0fa4ba94d2b5b44d5348701177c0cf8931bdc93 From 17f113d30f11fdc9c0ef0854777bfc1176797946 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 6 Jun 2014 15:25:15 +0200 Subject: [PATCH 86/94] Add configuration of AVPF in the proxy config GTK dialog. --- gtk/propertybox.c | 10 +++++++++ gtk/sip_account.ui | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/gtk/propertybox.c b/gtk/propertybox.c index 81d3db345..23211fa3b 100644 --- a/gtk/propertybox.c +++ b/gtk/propertybox.c @@ -922,6 +922,10 @@ void linphone_gtk_show_proxy_config(GtkWidget *pb, LinphoneProxyConfig *cfg){ linphone_proxy_config_register_enabled(cfg)); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(linphone_gtk_get_widget(w,"publish")), linphone_proxy_config_publish_enabled(cfg)); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(linphone_gtk_get_widget(w,"avpf")), + linphone_proxy_config_avpf_enabled(cfg)); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(linphone_gtk_get_widget(w,"avpf_rr_interval")), + linphone_proxy_config_get_avpf_rr_interval(cfg)); } g_object_set_data(G_OBJECT(w),"config",(gpointer)cfg); g_object_set_data(G_OBJECT(w),"parameters",(gpointer)pb); @@ -978,6 +982,12 @@ void linphone_gtk_proxy_ok(GtkButton *button){ linphone_proxy_config_enable_register(cfg, gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(linphone_gtk_get_widget(w,"register")))); + linphone_proxy_config_enable_avpf(cfg, + gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(linphone_gtk_get_widget(w,"avpf")))); + linphone_proxy_config_set_avpf_rr_interval(cfg, + (int)gtk_spin_button_get_value( + GTK_SPIN_BUTTON(linphone_gtk_get_widget(w,"avpf_rr_interval")))); /* check if tls was asked but is not enabled in transport configuration*/ if (tport==LinphoneTransportTls){ diff --git a/gtk/sip_account.ui b/gtk/sip_account.ui index d0a93d5dc..6d03932d1 100644 --- a/gtk/sip_account.ui +++ b/gtk/sip_account.ui @@ -8,6 +8,13 @@ 1 10 + + 5 + 1 + 5 + 1 + 1 + False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -235,6 +242,37 @@ 6 + + + True + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + AVPF regular RTCP interval (sec): + right + + + 6 + 7 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + False + True + True + adjustment2 + + + 1 + 2 + 6 + 7 + + True @@ -311,6 +349,22 @@ 2 + + + Enable AVPF + True + True + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + True + + + False + True + 3 + + From 33794b1a1453c3ad848a5aa6c3df76c5155127e8 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Fri, 6 Jun 2014 15:58:47 +0200 Subject: [PATCH 87/94] update mediastreamer2 and ortp (loss rate estimator) --- mediastreamer2 | 2 +- oRTP | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mediastreamer2 b/mediastreamer2 index c3529c41b..4b156f31e 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit c3529c41b46ae5e099566462b3fb7f055441ddfb +Subproject commit 4b156f31e41eab5ac34dd0cefffa9ff40eeefc0c diff --git a/oRTP b/oRTP index f0fa4ba94..9d85ca0e1 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit f0fa4ba94d2b5b44d5348701177c0cf8931bdc93 +Subproject commit 9d85ca0e1a117a2fbfb02de8df3b19bd5eb5db81 From 134a4cd91489858598570a6ee1331c6ee5863297 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Fri, 6 Jun 2014 14:52:05 +0200 Subject: [PATCH 88/94] fix invalid memory access and add warning about memory leak in quality reporting --- coreapi/quality_reporting.c | 24 ++++++++++++------------ tester/call_tester.c | 10 ++++++---- tester/liblinphone_tester.c | 2 +- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c index 102f38c9e..e0f21d377 100644 --- a/coreapi/quality_reporting.c +++ b/coreapi/quality_reporting.c @@ -31,20 +31,19 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /*************************************************************************** * TODO / REMINDER LIST *************************************************************************** -For codecs that are able to change sample rates, the lowest and highest sample rates MUST be reported (e.g., 8000;16000). -moslq == moscq -valgrind -video: what happens if doing stop/resume? -one single report <- merge audio/video? -rlq value: need algo to compute it -3.4 overload avoidance? + For codecs that are able to change sample rates, the lowest and highest sample rates MUST be reported (e.g., 8000;16000). + moslq == moscq + rlq value: need algo to compute it - - The Session report when session terminates, media change (codec change or a session fork), session terminates due to no media packets being received - - The Interval report SHOULD be used for periodic or interval reporting + 3.4 overload avoidance? + a. Send only one report at the end of each call. (audio | video?) + b. Use interval reports only on "problem" calls that are being closely monitored. - -> avg values - -> interval report - -> custom metrics + + - The Session report when + codec change + session fork + video enable/disable <-- what happens if doing stop/resume? *************************************************************************** * END OF TODO / REMINDER LIST ****************************************************************************/ @@ -340,6 +339,7 @@ static void send_report(const LinphoneCall* call, reporting_session_report_t * r content.data = buffer; content.size = strlen(buffer); + /*(WIP) Memory leak: PUBLISH message is never freed (issue 1283)*/ linphone_core_publish(call->core, addr, "vq-rtcpxr", expires, &content); linphone_address_destroy(addr); diff --git a/tester/call_tester.c b/tester/call_tester.c index bede109ed..0548c7c1e 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -576,7 +576,7 @@ static bool_t check_ice(LinphoneCoreManager* caller, LinphoneCoreManager* callee } ms_usleep(50000); } - + if (video_enabled){ for (i=0;i<200;i++){ if ((c1 != NULL) && (c2 != NULL)) { @@ -967,7 +967,7 @@ static void video_call_base(LinphoneCoreManager* pauline,LinphoneCoreManager* ma CU_ASSERT_TRUE(call_with_params(pauline,marie,caller_params,callee_params)); marie_call=linphone_core_get_current_call(marie->lc); pauline_call=linphone_core_get_current_call(pauline->lc); - + linphone_call_params_destroy(caller_params); linphone_call_params_destroy(callee_params); @@ -2213,14 +2213,16 @@ static void quality_reporting_at_call_termination() { create_call_for_quality_reporting_tests(marie, pauline, &call_marie, &call_pauline); linphone_core_terminate_all_calls(marie->lc); + + // now dialog id should be filled + CU_ASSERT_PTR_NOT_NULL(call_marie->log->reports[0]->dialog_id); + CU_ASSERT_TRUE(wait_for_until(marie->lc,pauline->lc,&marie->stat.number_of_LinphoneCallReleased,1, 10000)); CU_ASSERT_TRUE(wait_for_until(pauline->lc,NULL,&pauline->stat.number_of_LinphoneCallReleased,1, 10000)); CU_ASSERT_PTR_NULL(linphone_core_get_current_call(marie->lc)); CU_ASSERT_PTR_NULL(linphone_core_get_current_call(pauline->lc)); - // now dialog id should be filled - CU_ASSERT_PTR_NOT_NULL(call_marie->log->reports[0]->dialog_id); // PUBLISH submission to the collector should be ok CU_ASSERT_TRUE(wait_for(marie->lc,NULL,&marie->stat.number_of_LinphonePublishProgress,1)); diff --git a/tester/liblinphone_tester.c b/tester/liblinphone_tester.c index c5d017566..825e16727 100644 --- a/tester/liblinphone_tester.c +++ b/tester/liblinphone_tester.c @@ -119,7 +119,7 @@ static void liblinphone_tester_qnx_log_handler(OrtpLogLevel lev, const char *fmt void helper(const char *name) { - fprintf(stderr,"%s \t--help\n" + fprintf(stderr,"%s --help\n" "\t\t\t--verbose\n" "\t\t\t--silent\n" "\t\t\t--list-suites\n" From 13ecaf7a91fddd49d5f556a1c479fd2861b7f663 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Fri, 6 Jun 2014 16:45:46 +0200 Subject: [PATCH 89/94] Quality reporting: fix RTCP-XR packets processing --- coreapi/quality_reporting.c | 57 ++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c index e0f21d377..25ef43059 100644 --- a/coreapi/quality_reporting.c +++ b/coreapi/quality_reporting.c @@ -32,18 +32,22 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * TODO / REMINDER LIST *************************************************************************** For codecs that are able to change sample rates, the lowest and highest sample rates MUST be reported (e.g., 8000;16000). - moslq == moscq + rlq value: need algo to compute it 3.4 overload avoidance? a. Send only one report at the end of each call. (audio | video?) b. Use interval reports only on "problem" calls that are being closely monitored. + move "on_action_suggested" stuff in init - The Session report when codec change session fork video enable/disable <-- what happens if doing stop/resume? + + + if BYE and continue received packet drop them *************************************************************************** * END OF TODO / REMINDER LIST ****************************************************************************/ @@ -250,8 +254,8 @@ static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * off /*if quality estimates metrics are available, rtcp_xr_count should be always not null*/ if ((available_metrics & METRICS_QUALITY_ESTIMATES) != 0){ - IF_NUM_IN_RANGE(rm.quality_estimates.moslq/rm.rtcp_xr_count, 1, 5, moslq_str = float_to_one_decimal_string(rm.quality_estimates.moslq)); - IF_NUM_IN_RANGE(rm.quality_estimates.moscq/rm.rtcp_xr_count, 1, 5, moscq_str = float_to_one_decimal_string(rm.quality_estimates.moscq)); + IF_NUM_IN_RANGE(rm.quality_estimates.moslq/rm.rtcp_xr_count, 1, 5, moslq_str = float_to_one_decimal_string(rm.quality_estimates.moslq/rm.rtcp_xr_count)); + IF_NUM_IN_RANGE(rm.quality_estimates.moscq/rm.rtcp_xr_count, 1, 5, moscq_str = float_to_one_decimal_string(rm.quality_estimates.moscq/rm.rtcp_xr_count)); append_to_buffer(buffer, size, offset, "\r\nQualityEst:"); APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RLQ=%d", rm.quality_estimates.rlq/rm.rtcp_xr_count, 1, 120); @@ -496,15 +500,12 @@ void linphone_reporting_on_rtcp_received(LinphoneCall *call, int stats_type) { if (stats.updated == LINPHONE_CALL_STATS_RECEIVED_RTCP_UPDATE) { metrics = &report->remote_metrics; - if (rtcp_is_XR(stats.received_rtcp) == TRUE) { - block = stats.received_rtcp; - } + block = stats.received_rtcp; } else if (stats.updated == LINPHONE_CALL_STATS_SENT_RTCP_UPDATE) { metrics = &report->local_metrics; - if (rtcp_is_XR(stats.sent_rtcp) == TRUE) { - block = stats.sent_rtcp; - } + block = stats.sent_rtcp; } + /*should not be done there*/ if (call->audiostream->ms.use_rc&&call->audiostream->ms.rc){ analyzer=ms_bitrate_controller_get_qos_analyzer(call->audiostream->ms.rc); if (analyzer){ @@ -514,33 +515,29 @@ void linphone_reporting_on_rtcp_received(LinphoneCall *call, int stats_type) { } } - if (block != NULL) { - switch (rtcp_XR_get_block_type(block)) { - case RTCP_XR_VOIP_METRICS: { - uint8_t config = rtcp_XR_voip_metrics_get_rx_config(block); + do{ + if (rtcp_is_XR(block) && (rtcp_XR_get_block_type(block) == RTCP_XR_VOIP_METRICS)){ - metrics->rtcp_xr_count++; + uint8_t config = rtcp_XR_voip_metrics_get_rx_config(block); - metrics->quality_estimates.rcq += rtcp_XR_voip_metrics_get_r_factor(block); - metrics->quality_estimates.moslq += rtcp_XR_voip_metrics_get_mos_lq(block) / 10.f; - metrics->quality_estimates.moscq += rtcp_XR_voip_metrics_get_mos_cq(block) / 10.f; + metrics->rtcp_xr_count++; - metrics->jitter_buffer.nominal += rtcp_XR_voip_metrics_get_jb_nominal(block); - metrics->jitter_buffer.max += rtcp_XR_voip_metrics_get_jb_maximum(block); - metrics->jitter_buffer.abs_max = rtcp_XR_voip_metrics_get_jb_abs_max(block); - metrics->jitter_buffer.adaptive = (config >> 4) & 0x3; - metrics->packet_loss.network_packet_loss_rate = rtcp_XR_voip_metrics_get_loss_rate(block); - metrics->packet_loss.jitter_buffer_discard_rate = rtcp_XR_voip_metrics_get_discard_rate(block); + metrics->quality_estimates.rcq += rtcp_XR_voip_metrics_get_r_factor(block); + metrics->quality_estimates.moslq += rtcp_XR_voip_metrics_get_mos_lq(block) / 10.f; + metrics->quality_estimates.moscq += rtcp_XR_voip_metrics_get_mos_cq(block) / 10.f; - metrics->session_description.packet_loss_concealment = (config >> 6) & 0x3; + metrics->jitter_buffer.nominal += rtcp_XR_voip_metrics_get_jb_nominal(block); + metrics->jitter_buffer.max += rtcp_XR_voip_metrics_get_jb_maximum(block); + metrics->jitter_buffer.abs_max = rtcp_XR_voip_metrics_get_jb_abs_max(block); + metrics->jitter_buffer.adaptive = (config >> 4) & 0x3; + metrics->packet_loss.network_packet_loss_rate = rtcp_XR_voip_metrics_get_loss_rate(block); + metrics->packet_loss.jitter_buffer_discard_rate = rtcp_XR_voip_metrics_get_discard_rate(block); - metrics->delay.round_trip_delay += rtcp_XR_voip_metrics_get_round_trip_delay(block); - break; - } default: { - break; - } + metrics->session_description.packet_loss_concealment = (config >> 6) & 0x3; + + metrics->delay.round_trip_delay += rtcp_XR_voip_metrics_get_round_trip_delay(block); } - } + }while(rtcp_next_packet(block)); /* check if we should send an interval report */ if (report_interval>0 && ms_time(NULL)-report->last_report_date>report_interval){ From 94da3680bedf037d81bceff3536071e79b1c18ee Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Fri, 6 Jun 2014 17:02:06 +0200 Subject: [PATCH 90/94] Quality reporting: factorizing some functions --- coreapi/quality_reporting.c | 73 ++++++++++++++++--------------------- tester/call_tester.c | 4 +- 2 files changed, 33 insertions(+), 44 deletions(-) diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c index 25ef43059..79557dc37 100644 --- a/coreapi/quality_reporting.c +++ b/coreapi/quality_reporting.c @@ -174,6 +174,20 @@ static uint8_t are_metrics_filled(const reporting_content_metrics_t rm) { return ret; } +static bool_t quality_reporting_enabled(const LinphoneCall * call) { + return (call->dest_proxy != NULL && linphone_proxy_config_quality_reporting_enabled(call->dest_proxy)); +} + +static bool_t media_report_enabled(LinphoneCall * call, int stats_type){ + if (! quality_reporting_enabled(call)) + return FALSE; + + if (stats_type == LINPHONE_CALL_STATS_VIDEO && !linphone_call_params_video_enabled(linphone_call_get_current_params(call))) + return FALSE; + + return (call->log->reports[stats_type] != NULL); +} + static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * offset, const reporting_content_metrics_t rm) { char * timestamps_start_str = NULL; char * timestamps_stop_str = NULL; @@ -360,16 +374,14 @@ static const SalStreamDescription * get_media_stream_for_desc(const SalMediaDesc } } } - if (smd == NULL || count == smd->n_total_streams) { - ms_warning("Could not find the associated stream of type %d", sal_stream_type); - } + ms_warning("Could not find the associated stream of type %d", sal_stream_type); return NULL; } static void update_ip(LinphoneCall * call, int stats_type) { SalStreamType sal_stream_type = (stats_type == LINPHONE_CALL_STATS_AUDIO) ? SalAudio : SalVideo; - if (call->log->reports[stats_type] != NULL) { + if (media_report_enabled(call,stats_type)) { const SalStreamDescription * local_desc = get_media_stream_for_desc(call->localdesc, sal_stream_type); const SalStreamDescription * remote_desc = get_media_stream_for_desc(sal_call_get_remote_media_description(call->op), sal_stream_type); @@ -393,10 +405,6 @@ static void update_ip(LinphoneCall * call, int stats_type) { } } -static bool_t is_reporting_enabled(const LinphoneCall * call) { - return (call->dest_proxy != NULL && linphone_proxy_config_quality_reporting_enabled(call->dest_proxy)); -} - static void qos_analyzer_on_action_suggested(void *user_data, const char * input, const char * output){ reporting_content_metrics_t *metrics = (reporting_content_metrics_t*) user_data; char * newstr = NULL; @@ -408,24 +416,18 @@ static void qos_analyzer_on_action_suggested(void *user_data, const char * input } void linphone_reporting_update_ip(LinphoneCall * call) { - if (! is_reporting_enabled(call)) - return; - update_ip(call, LINPHONE_CALL_STATS_AUDIO); - - if (linphone_call_params_video_enabled(linphone_call_get_current_params(call))) { - update_ip(call, LINPHONE_CALL_STATS_VIDEO); - } + update_ip(call, LINPHONE_CALL_STATS_VIDEO); } void linphone_reporting_update_media_info(LinphoneCall * call, int stats_type) { - reporting_session_report_t * report = call->log->reports[stats_type]; MediaStream * stream = NULL; const PayloadType * local_payload = NULL; const PayloadType * remote_payload = NULL; const LinphoneCallParams * current_params = linphone_call_get_current_params(call); + reporting_session_report_t * report = call->log->reports[stats_type]; - if (! is_reporting_enabled(call)) + if (!media_report_enabled(call, stats_type)) return; STR_REASSIGN(report->info.call_id, ms_strdup(call->log->call_id)); @@ -495,7 +497,7 @@ void linphone_reporting_on_rtcp_received(LinphoneCall *call, int stats_type) { int report_interval = linphone_proxy_config_get_quality_reporting_interval(call->dest_proxy); - if (! is_reporting_enabled(call)) + if (! media_report_enabled(call,stats_type)) return; if (stats.updated == LINPHONE_CALL_STATS_RECEIVED_RTCP_UPDATE) { @@ -545,34 +547,21 @@ void linphone_reporting_on_rtcp_received(LinphoneCall *call, int stats_type) { } } +static void publish_report(LinphoneCall *call, const char *event_type){ + int i; + for (i = 0; i < 2; i++){ + if (media_report_enabled(call, i)){ + linphone_reporting_update_media_info(call, i); + send_report(call, call->log->reports[i], event_type); + } + } +} void linphone_reporting_publish_session_report(LinphoneCall* call) { - if (! is_reporting_enabled(call)) - return; - - if (call->log->reports[LINPHONE_CALL_STATS_AUDIO] != NULL) { - send_report(call, call->log->reports[LINPHONE_CALL_STATS_AUDIO], "VQSessionReport: CallTerm"); - } - - if (call->log->reports[LINPHONE_CALL_STATS_VIDEO] != NULL - && linphone_call_params_video_enabled(linphone_call_get_current_params(call))) { - send_report(call, call->log->reports[LINPHONE_CALL_STATS_VIDEO], "VQSessionReport: CallTerm"); - } + publish_report(call, "VQSessionReport: CallTerm"); } void linphone_reporting_publish_interval_report(LinphoneCall* call) { - if (! is_reporting_enabled(call)) - return; - - if (call->log->reports[LINPHONE_CALL_STATS_AUDIO] != NULL) { - linphone_reporting_update_media_info(call, LINPHONE_CALL_STATS_AUDIO); - send_report(call, call->log->reports[LINPHONE_CALL_STATS_AUDIO], "VQIntervalReport"); - } - - if (call->log->reports[LINPHONE_CALL_STATS_VIDEO] != NULL - && linphone_call_params_video_enabled(linphone_call_get_current_params(call))) { - linphone_reporting_update_media_info(call, LINPHONE_CALL_STATS_VIDEO); - send_report(call, call->log->reports[LINPHONE_CALL_STATS_VIDEO], "VQIntervalReport"); - } + publish_report(call, "VQIntervalReport"); } reporting_session_report_t * linphone_reporting_new() { diff --git a/tester/call_tester.c b/tester/call_tester.c index 0548c7c1e..b5feb834e 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -2245,8 +2245,8 @@ static void quality_reporting_interval_report() { CU_ASSERT_PTR_NOT_NULL(linphone_core_get_current_call(pauline->lc)); // PUBLISH submission to the collector should be ok - CU_ASSERT_TRUE(wait_for_until(marie->lc,pauline->lc,&marie->stat.number_of_LinphonePublishProgress,3,15000)); - CU_ASSERT_TRUE(wait_for_until(marie->lc,pauline->lc,&marie->stat.number_of_LinphonePublishOk,3,15000)); + CU_ASSERT_TRUE(wait_for_until(marie->lc,pauline->lc,&marie->stat.number_of_LinphonePublishProgress,3,25000)); + CU_ASSERT_TRUE(wait_for_until(marie->lc,pauline->lc,&marie->stat.number_of_LinphonePublishOk,3,25000)); linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); From 44aa4a821b8e2dac8b77aed3900d9d51505439d7 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 9 Jun 2014 11:02:13 +0200 Subject: [PATCH 91/94] Hide wizard button in preferences if Linphone has been build without wizard support. --- gtk/propertybox.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gtk/propertybox.c b/gtk/propertybox.c index 23211fa3b..7ed960c46 100644 --- a/gtk/propertybox.c +++ b/gtk/propertybox.c @@ -1483,6 +1483,8 @@ void linphone_gtk_show_parameters(void){ } #ifdef BUILD_WIZARD gtk_widget_show(linphone_gtk_get_widget(pb,"wizard")); +#else + gtk_widget_hide(linphone_gtk_get_widget(pb,"wizard")); #endif linphone_gtk_show_sip_accounts(pb); /* CODECS CONFIG */ From c913816dee591ea7d8feff14b2f95e016bd5f88a Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Mon, 9 Jun 2014 11:04:41 +0200 Subject: [PATCH 92/94] java synchronize LinphoneEvent and LinphoneChatRoom APIs with LinphoneCore --- coreapi/chat.c | 8 ++ coreapi/linphonecore.h | 1 + coreapi/linphonecore_jni.cc | 15 ++++ .../org/linphone/core/LinphoneChatRoom.java | 5 ++ .../org/linphone/core/LinphoneEvent.java | 6 ++ .../linphone/core/LinphoneChatRoomImpl.java | 5 ++ .../org/linphone/core/LinphoneCoreImpl.java | 12 +-- .../org/linphone/core/LinphoneEventImpl.java | 84 ++++++++++++------- 8 files changed, 99 insertions(+), 37 deletions(-) diff --git a/coreapi/chat.c b/coreapi/chat.c index c20cfef9e..9e2ce9a69 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -387,11 +387,19 @@ bool_t linphone_chat_room_is_remote_composing(const LinphoneChatRoom *cr) { /** * Returns back pointer to LinphoneCore object. + * @deprecated use linphone_chat_room_get_core() **/ LinphoneCore* linphone_chat_room_get_lc(LinphoneChatRoom *cr){ return cr->lc; } +/** + * Returns back pointer to LinphoneCore object. +**/ +LinphoneCore* linphone_chat_room_get_core(LinphoneChatRoom *cr){ + return cr->lc; +} + /** * Assign a user pointer to the chat room. **/ diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index debf48461..c35776c1d 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -1158,6 +1158,7 @@ LINPHONE_PUBLIC bool_t linphone_chat_room_is_remote_composing(const LinphoneChat LINPHONE_PUBLIC int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr); LINPHONE_PUBLIC LinphoneCore* linphone_chat_room_get_lc(LinphoneChatRoom *cr); +LINPHONE_PUBLIC LinphoneCore* linphone_chat_room_get_core(LinphoneChatRoom *cr); LINPHONE_PUBLIC void linphone_chat_room_set_user_data(LinphoneChatRoom *cr, void * ud); LINPHONE_PUBLIC void * linphone_chat_room_get_user_data(LinphoneChatRoom *cr); LINPHONE_PUBLIC MSList* linphone_core_get_chat_rooms(LinphoneCore *lc); diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index a24d2c589..919c813a9 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -2630,6 +2630,15 @@ static void chat_room_impl_callback(LinphoneChatMessage* msg, LinphoneChatMessag linphone_chat_message_set_user_data(msg,NULL); } } + +extern "C" jobject Java_org_linphone_core_LinphoneChatRoomImpl_getCore(JNIEnv* env + ,jobject thiz + ,jlong chatroom_ptr){ + LinphoneCore *lc=linphone_chat_room_get_core((LinphoneChatRoom*)chatroom_ptr); + LinphoneCoreData *lcd=(LinphoneCoreData*)linphone_core_get_user_data(lc); + return lcd->core; +} + extern "C" void Java_org_linphone_core_LinphoneChatRoomImpl_sendMessage2(JNIEnv* env ,jobject thiz ,jlong chatroom_ptr @@ -3669,6 +3678,12 @@ JNIEXPORT void JNICALL Java_org_linphone_core_LinphoneCoreFactoryImpl__1setLogHa } } +JNIEXPORT jobject JNICALL Java_org_linphone_core_LinphoneEventImpl_getCore(JNIEnv *env, jobject jobj, jlong evptr){ + LinphoneCore *lc=linphone_event_get_core((LinphoneEvent*)evptr); + LinphoneCoreData *lcd=(LinphoneCoreData*)linphone_core_get_user_data(lc); + return lcd->core; +} + /* * Class: org_linphone_core_LinphoneEventImpl * Method: getEventName diff --git a/java/common/org/linphone/core/LinphoneChatRoom.java b/java/common/org/linphone/core/LinphoneChatRoom.java index e84538269..c1e635c0a 100644 --- a/java/common/org/linphone/core/LinphoneChatRoom.java +++ b/java/common/org/linphone/core/LinphoneChatRoom.java @@ -114,4 +114,9 @@ public interface LinphoneChatRoom { * @return LinphoneChatMessage object */ LinphoneChatMessage createLinphoneChatMessage(String message, String url, State state, long timestamp, boolean isRead, boolean isIncoming); + /** + * Returns a back pointer to the core managing the chat room. + * @return the LinphoneCore + */ + LinphoneCore getCore(); } diff --git a/java/common/org/linphone/core/LinphoneEvent.java b/java/common/org/linphone/core/LinphoneEvent.java index dcdfcfc48..cca9a3f2d 100644 --- a/java/common/org/linphone/core/LinphoneEvent.java +++ b/java/common/org/linphone/core/LinphoneEvent.java @@ -100,4 +100,10 @@ public interface LinphoneEvent { * @param body the new data to be published */ void sendPublish(LinphoneContent body); + + /** + * Get a back pointer to the LinphoneCore object managing this LinphoneEvent. + * @return + */ + LinphoneCore getCore(); } diff --git a/java/impl/org/linphone/core/LinphoneChatRoomImpl.java b/java/impl/org/linphone/core/LinphoneChatRoomImpl.java index 7901272c9..003cdd076 100644 --- a/java/impl/org/linphone/core/LinphoneChatRoomImpl.java +++ b/java/impl/org/linphone/core/LinphoneChatRoomImpl.java @@ -119,4 +119,9 @@ class LinphoneChatRoomImpl implements LinphoneChatRoom { return new LinphoneChatMessageImpl(createLinphoneChatMessage2( nativePtr, message, url, state.value(), timestamp / 1000, isRead, isIncoming)); } + private native Object getCore(long nativePtr); + @Override + public LinphoneCore getCore() { + return (LinphoneCore)getCore(nativePtr); + } } diff --git a/java/impl/org/linphone/core/LinphoneCoreImpl.java b/java/impl/org/linphone/core/LinphoneCoreImpl.java index 8cd5fe79e..bf43810a3 100644 --- a/java/impl/org/linphone/core/LinphoneCoreImpl.java +++ b/java/impl/org/linphone/core/LinphoneCoreImpl.java @@ -610,13 +610,13 @@ class LinphoneCoreImpl implements LinphoneCore { public synchronized void enableEchoLimiter(boolean val) { enableEchoLimiter(nativePtr,val); } - public void setVideoDevice(int id) { + public synchronized void setVideoDevice(int id) { Log.i("Setting camera id :", id); if (setVideoDevice(nativePtr, id) != 0) { Log.e("Failed to set video device to id:", id); } } - public int getVideoDevice() { + public synchronized int getVideoDevice() { return getVideoDevice(nativePtr); } @@ -847,7 +847,7 @@ class LinphoneCoreImpl implements LinphoneCore { private native void tunnelSetHttpProxy(long nativePtr, String proxy_host, int port, String username, String password); @Override - public void tunnelSetHttpProxy(String proxy_host, int port, + public synchronized void tunnelSetHttpProxy(String proxy_host, int port, String username, String password) { tunnelSetHttpProxy(nativePtr, proxy_host, port, username, password); } @@ -1185,17 +1185,17 @@ class LinphoneCoreImpl implements LinphoneCore { } @Override - public void stopRinging() { + public synchronized void stopRinging() { stopRinging(nativePtr); } private native void setPayloadTypeBitrate(long coreptr, long payload_ptr, int bitrate); @Override - public void setPayloadTypeBitrate(PayloadType pt, int bitrate) { + public synchronized void setPayloadTypeBitrate(PayloadType pt, int bitrate) { setPayloadTypeBitrate(nativePtr, ((PayloadTypeImpl)pt).nativePtr, bitrate); } private native int getPayloadTypeBitrate(long coreptr, long payload_ptr); @Override - public int getPayloadTypeBitrate(PayloadType pt) { + public synchronized int getPayloadTypeBitrate(PayloadType pt) { return getPayloadTypeBitrate(nativePtr, ((PayloadTypeImpl)pt).nativePtr); } diff --git a/java/impl/org/linphone/core/LinphoneEventImpl.java b/java/impl/org/linphone/core/LinphoneEventImpl.java index 2b4c1a71b..ca9c2151c 100644 --- a/java/impl/org/linphone/core/LinphoneEventImpl.java +++ b/java/impl/org/linphone/core/LinphoneEventImpl.java @@ -11,71 +11,83 @@ public class LinphoneEventImpl implements LinphoneEvent { private native String getEventName(long nativeptr); @Override - public String getEventName() { + public synchronized String getEventName() { return getEventName(mNativePtr); } private native int acceptSubscription(long nativeptr); @Override - public void acceptSubscription() { - acceptSubscription(mNativePtr); + public synchronized void acceptSubscription() { + synchronized(getCore()){ + acceptSubscription(mNativePtr); + } } private native int denySubscription(long nativeptr, int reason); @Override - public void denySubscription(Reason reason) { - denySubscription(mNativePtr,reason.mValue); + public synchronized void denySubscription(Reason reason) { + synchronized(getCore()){ + denySubscription(mNativePtr,reason.mValue); + } } private native int notify(long nativeptr, String type, String subtype, byte data[], String encoding); @Override - public void notify(LinphoneContent content) { - notify(mNativePtr,content.getType(),content.getSubtype(),content.getData(),content.getEncoding()); + public synchronized void notify(LinphoneContent content) { + synchronized(getCore()){ + notify(mNativePtr,content.getType(),content.getSubtype(),content.getData(),content.getEncoding()); + } } private native int updateSubscribe(long nativePtr, String type, String subtype, byte data[], String encoding); @Override - public void updateSubscribe(LinphoneContent content) { - updateSubscribe(mNativePtr,content.getType(), content.getSubtype(),content.getData(),content.getEncoding()); + public synchronized void updateSubscribe(LinphoneContent content) { + synchronized(getCore()){ + updateSubscribe(mNativePtr,content.getType(), content.getSubtype(),content.getData(),content.getEncoding()); + } } private native int updatePublish(long nativePtr, String type, String subtype, byte data[], String encoding); @Override - public void updatePublish(LinphoneContent content) { - updatePublish(mNativePtr,content.getType(), content.getSubtype(),content.getData(),content.getEncoding()); + public synchronized void updatePublish(LinphoneContent content) { + synchronized(getCore()){ + updatePublish(mNativePtr,content.getType(), content.getSubtype(),content.getData(),content.getEncoding()); + } } private native int terminate(long nativePtr); @Override - public void terminate() { - terminate(mNativePtr); + public synchronized void terminate() { + synchronized(getCore()){ + terminate(mNativePtr); + } } private native int getReason(long nativePtr); @Override - public Reason getReason() { + public synchronized Reason getReason() { return Reason.fromInt(getReason(mNativePtr)); } @Override - public void setUserContext(Object obj) { + public synchronized void setUserContext(Object obj) { mUserContext=obj; } @Override - public Object getUserContext() { + public synchronized Object getUserContext() { return mUserContext; } private native int getSubscriptionDir(long nativeptr); @Override - public SubscriptionDir getSubscriptionDir() { + public synchronized SubscriptionDir getSubscriptionDir() { return SubscriptionDir.fromInt(getSubscriptionDir(mNativePtr)); } private native int getSubscriptionState(long nativeptr); @Override - public SubscriptionState getSubscriptionState() { + public synchronized SubscriptionState getSubscriptionState() { try { return SubscriptionState.fromInt(getSubscriptionState(mNativePtr)); } catch (LinphoneCoreException e) { @@ -91,37 +103,47 @@ public class LinphoneEventImpl implements LinphoneEvent { private native void addCustomHeader(long ptr, String name, String value); @Override - public void addCustomHeader(String name, String value) { + public synchronized void addCustomHeader(String name, String value) { addCustomHeader(mNativePtr, name, value); } private native String getCustomHeader(long ptr, String name); @Override - public String getCustomHeader(String name) { + public synchronized String getCustomHeader(String name) { return getCustomHeader(mNativePtr, name); } private native void sendSubscribe(long ptr, String type, String subtype, byte data [], String encoding); @Override - public void sendSubscribe(LinphoneContent body) { - if (body != null) - sendSubscribe(mNativePtr, body.getType(), body.getSubtype(), body.getData(), body.getEncoding()); - else - sendSubscribe(mNativePtr, null, null, null, null); + public synchronized void sendSubscribe(LinphoneContent body) { + synchronized(getCore()){ + if (body != null) + sendSubscribe(mNativePtr, body.getType(), body.getSubtype(), body.getData(), body.getEncoding()); + else + sendSubscribe(mNativePtr, null, null, null, null); + } } private native void sendPublish(long ptr, String type, String subtype, byte data [], String encoding); @Override - public void sendPublish(LinphoneContent body) { - if (body != null) - sendPublish(mNativePtr, body.getType(), body.getSubtype(), body.getData(), body.getEncoding()); - else - sendPublish(mNativePtr, null, null, null, null); + public synchronized void sendPublish(LinphoneContent body) { + synchronized(getCore()){ + if (body != null) + sendPublish(mNativePtr, body.getType(), body.getSubtype(), body.getData(), body.getEncoding()); + else + sendPublish(mNativePtr, null, null, null, null); + } } private native long getErrorInfo(long nativePtr); @Override - public ErrorInfo getErrorInfo() { + public synchronized ErrorInfo getErrorInfo() { return new ErrorInfoImpl(getErrorInfo(mNativePtr)); } + private native Object getCore(long nativePtr); + @Override + public synchronized LinphoneCore getCore() { + return (LinphoneCore)getCore(mNativePtr); + } + } From 7d3031dd2304de7e95654ad1a9b0deb77ea66678 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Mon, 9 Jun 2014 11:52:53 +0200 Subject: [PATCH 93/94] synchronisation in chatroom (lost in previous commit) --- .../linphone/core/LinphoneChatRoomImpl.java | 124 +++++++++++------- 1 file changed, 75 insertions(+), 49 deletions(-) diff --git a/java/impl/org/linphone/core/LinphoneChatRoomImpl.java b/java/impl/org/linphone/core/LinphoneChatRoomImpl.java index 003cdd076..8aa9c258d 100644 --- a/java/impl/org/linphone/core/LinphoneChatRoomImpl.java +++ b/java/impl/org/linphone/core/LinphoneChatRoomImpl.java @@ -44,84 +44,110 @@ class LinphoneChatRoomImpl implements LinphoneChatRoom { nativePtr = aNativePtr; } - public LinphoneAddress getPeerAddress() { + public synchronized LinphoneAddress getPeerAddress() { return new LinphoneAddressImpl(getPeerAddress(nativePtr),LinphoneAddressImpl.WrapMode.FromConst); } - public void sendMessage(String message) { - sendMessage(nativePtr,message); - } - - @Override - public void sendMessage(LinphoneChatMessage message, StateListener listener) { - sendMessage2(nativePtr, message, ((LinphoneChatMessageImpl)message).getNativePtr(), listener); - } - - @Override - public LinphoneChatMessage createLinphoneChatMessage(String message) { - return new LinphoneChatMessageImpl(createLinphoneChatMessage(nativePtr, message)); - } - - public LinphoneChatMessage[] getHistory() { - return getHistory(0); - } - - public LinphoneChatMessage[] getHistory(int limit) { - long[] typesPtr = getHistory(nativePtr, limit); - if (typesPtr == null) return null; - - LinphoneChatMessage[] messages = new LinphoneChatMessage[typesPtr.length]; - for (int i=0; i < messages.length; i++) { - messages[i] = new LinphoneChatMessageImpl(typesPtr[i]); + public synchronized void sendMessage(String message) { + synchronized(getCore()){ + sendMessage(nativePtr,message); } - - return messages; } - public void destroy() { + @Override + public synchronized void sendMessage(LinphoneChatMessage message, StateListener listener) { + synchronized(getCore()){ + sendMessage2(nativePtr, message, ((LinphoneChatMessageImpl)message).getNativePtr(), listener); + } + } + + @Override + public synchronized LinphoneChatMessage createLinphoneChatMessage(String message) { + synchronized(getCore()){ + return new LinphoneChatMessageImpl(createLinphoneChatMessage(nativePtr, message)); + } + } + + public synchronized LinphoneChatMessage[] getHistory() { + synchronized(getCore()){ + return getHistory(0); + } + } + + public synchronized LinphoneChatMessage[] getHistory(int limit) { + synchronized(getCore()){ + long[] typesPtr = getHistory(nativePtr, limit); + if (typesPtr == null) return null; + + LinphoneChatMessage[] messages = new LinphoneChatMessage[typesPtr.length]; + for (int i=0; i < messages.length; i++) { + messages[i] = new LinphoneChatMessageImpl(typesPtr[i]); + } + + return messages; + } + } + + public synchronized void destroy() { destroy(nativePtr); } - public int getUnreadMessagesCount() { - return getUnreadMessagesCount(nativePtr); + public synchronized int getUnreadMessagesCount() { + synchronized(getCore()){ + return getUnreadMessagesCount(nativePtr); + } } - public void deleteHistory() { - deleteHistory(nativePtr); + public synchronized void deleteHistory() { + synchronized(getCore()){ + deleteHistory(nativePtr); + } } - public void compose() { - compose(nativePtr); + public synchronized void compose() { + synchronized(getCore()){ + compose(nativePtr); + } } - public boolean isRemoteComposing() { - return isRemoteComposing(nativePtr); + public synchronized boolean isRemoteComposing() { + synchronized(getCore()){ + return isRemoteComposing(nativePtr); + } } - public void markAsRead() { - markAsRead(nativePtr); + public synchronized void markAsRead() { + synchronized(getCore()){ + markAsRead(nativePtr); + } } - public void deleteMessage(LinphoneChatMessage message) { - if (message != null) - deleteMessage(nativePtr, ((LinphoneChatMessageImpl)message).getNativePtr()); + public synchronized void deleteMessage(LinphoneChatMessage message) { + synchronized(getCore()){ + if (message != null) + deleteMessage(nativePtr, ((LinphoneChatMessageImpl)message).getNativePtr()); + } } - public void updateUrl(LinphoneChatMessage message) { - if (message != null) - updateUrl(nativePtr, ((LinphoneChatMessageImpl)message).getNativePtr()); + public synchronized void updateUrl(LinphoneChatMessage message) { + synchronized(getCore()){ + if (message != null) + updateUrl(nativePtr, ((LinphoneChatMessageImpl)message).getNativePtr()); + } } @Override - public LinphoneChatMessage createLinphoneChatMessage(String message, + public synchronized LinphoneChatMessage createLinphoneChatMessage(String message, String url, State state, long timestamp, boolean isRead, boolean isIncoming) { - return new LinphoneChatMessageImpl(createLinphoneChatMessage2( - nativePtr, message, url, state.value(), timestamp / 1000, isRead, isIncoming)); + synchronized(getCore()){ + return new LinphoneChatMessageImpl(createLinphoneChatMessage2( + nativePtr, message, url, state.value(), timestamp / 1000, isRead, isIncoming)); + } } private native Object getCore(long nativePtr); @Override - public LinphoneCore getCore() { + public synchronized LinphoneCore getCore() { return (LinphoneCore)getCore(nativePtr); } } From ba970baa9569a03aed531d4a53c035eb22219c45 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Mon, 9 Jun 2014 10:19:54 +0200 Subject: [PATCH 94/94] Quality reporting: split adaptive algorithm values and captions --- coreapi/quality_reporting.c | 21 +++++++++++++++------ coreapi/quality_reporting.h | 2 ++ mediastreamer2 | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c index 79557dc37..2fd4229c3 100644 --- a/coreapi/quality_reporting.c +++ b/coreapi/quality_reporting.c @@ -158,7 +158,9 @@ static uint8_t are_metrics_filled(const reporting_content_metrics_t rm) { if (rm.signal.level != 127) ret|=METRICS_SIGNAL; if (rm.signal.noise_level != 127) ret|=METRICS_SIGNAL; + if (rm.qos_analyzer.input_leg!=NULL) ret|=METRICS_ADAPTIVE_ALGORITHM; if (rm.qos_analyzer.input!=NULL) ret|=METRICS_ADAPTIVE_ALGORITHM; + if (rm.qos_analyzer.output_leg!=NULL) ret|=METRICS_ADAPTIVE_ALGORITHM; if (rm.qos_analyzer.output!=NULL) ret|=METRICS_ADAPTIVE_ALGORITHM; if (rm.rtcp_xr_count>0){ @@ -289,7 +291,9 @@ static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * off if ((available_metrics & METRICS_ADAPTIVE_ALGORITHM) != 0){ append_to_buffer(buffer, size, offset, "\r\nAdaptiveAlg:"); + APPEND_IF_NOT_NULL_STR(buffer, size, offset, " IN_LEG=%s", rm.qos_analyzer.input_leg); APPEND_IF_NOT_NULL_STR(buffer, size, offset, " IN=%s", rm.qos_analyzer.input); + APPEND_IF_NOT_NULL_STR(buffer, size, offset, " OUT_LEG=%s", rm.qos_analyzer.output_leg); APPEND_IF_NOT_NULL_STR(buffer, size, offset, " OUT=%s", rm.qos_analyzer.output); } @@ -405,14 +409,17 @@ static void update_ip(LinphoneCall * call, int stats_type) { } } -static void qos_analyzer_on_action_suggested(void *user_data, const char * input, const char * output){ +static void qos_analyzer_on_action_suggested(void *user_data, int datac, const char** data){ reporting_content_metrics_t *metrics = (reporting_content_metrics_t*) user_data; - char * newstr = NULL; - newstr = ms_strdup_printf("%s%s;", metrics->qos_analyzer.input?metrics->qos_analyzer.input:"", input); - STR_REASSIGN(metrics->qos_analyzer.input, newstr) + char * appendbuf; - newstr = ms_strdup_printf("%s%s;", metrics->qos_analyzer.output?metrics->qos_analyzer.output:"", output); - STR_REASSIGN(metrics->qos_analyzer.output, newstr) + STR_REASSIGN(metrics->qos_analyzer.input_leg, ms_strdup(data[0])); + appendbuf=ms_strdup_printf("%s%s;", metrics->qos_analyzer.input?metrics->qos_analyzer.input:"", data[1]); + STR_REASSIGN(metrics->qos_analyzer.input,appendbuf); + + STR_REASSIGN(metrics->qos_analyzer.output_leg, ms_strdup(data[2])); + appendbuf=ms_strdup_printf("%s%s;", metrics->qos_analyzer.output?metrics->qos_analyzer.output:"", data[3]); + STR_REASSIGN(metrics->qos_analyzer.output, appendbuf); } void linphone_reporting_update_ip(LinphoneCall * call) { @@ -614,7 +621,9 @@ void linphone_reporting_destroy(reporting_session_report_t * report) { if (report->local_metrics.session_description.payload_desc != NULL) ms_free(report->local_metrics.session_description.payload_desc); if (report->remote_metrics.session_description.fmtp != NULL) ms_free(report->remote_metrics.session_description.fmtp); if (report->remote_metrics.session_description.payload_desc != NULL) ms_free(report->remote_metrics.session_description.payload_desc); + if (report->local_metrics.qos_analyzer.input_leg != NULL) ms_free(report->local_metrics.qos_analyzer.input_leg); if (report->local_metrics.qos_analyzer.input != NULL) ms_free(report->local_metrics.qos_analyzer.input); + if (report->local_metrics.qos_analyzer.output_leg != NULL) ms_free(report->local_metrics.qos_analyzer.output_leg); if (report->local_metrics.qos_analyzer.output != NULL) ms_free(report->local_metrics.qos_analyzer.output); ms_free(report); diff --git a/coreapi/quality_reporting.h b/coreapi/quality_reporting.h index 2dc888a89..c909baecc 100644 --- a/coreapi/quality_reporting.h +++ b/coreapi/quality_reporting.h @@ -98,7 +98,9 @@ typedef struct reporting_content_metrics { /* This should allow us to analysis bad network conditions and quality adaptation on server side*/ struct { + char* input_leg; char* input; + char* output_leg; char* output; } qos_analyzer; diff --git a/mediastreamer2 b/mediastreamer2 index 4b156f31e..0aee9ceea 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 4b156f31e41eab5ac34dd0cefffa9ff40eeefc0c +Subproject commit 0aee9ceeaa0f766c8d0e15ee2bcb82e06c7c541d

v8G!Em;N={Zi}jXHj}D68OU;vsNV@1 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%0Do~f7 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%0A{>>au9l6 z{QXTaptF4$EfMMMd)g|?NH_&qU;5KhNFTVuQymr=Q?B81aPEV`w^02Z(Ek%Zg2;cm z+{(`X|EZ$(P1S#2U99~1%FV@3ozL}uU4QnurkG?8Huh<1Ao@H$V;X?&(XON<`KJ>NIA{sQFZ~Hj-gZN%@G^G?Fr9nInYaSo1PFxr`!Ea{VJY zp-Svr@mdJIsdxn=aanVmwHzB?7_IV#+K4CDp?RGFxZ{I9?O*>46dC`S^)n^$0>G^~ z3QoLUYL2m(=^W!vn31TCkz`)6G$%S_g>(h!iHX@I&745HlJne--ygZc{|_X%y8nlZ zhl=Wds@`Aubn&Zvdfp$6T2r*OlR3$ZL=ucAAZ>~c?jeMT++<6X7B^fP&t~Ki@xg+- z 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