From 3f3c114a3ff22bd89608452080182da1ccaedcc1 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 28 Jul 2017 10:20:28 +0200 Subject: [PATCH 01/16] Fix build for iOS. --- coreapi/quality_reporting.c | 2 +- coreapi/ringtoneplayer_ios.h | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c index b0f4ecec6..9e8302dda 100644 --- a/coreapi/quality_reporting.c +++ b/coreapi/quality_reporting.c @@ -337,7 +337,7 @@ static int send_report(LinphoneCall* call, reporting_session_report_t * report, size_t namesize; char *machine; sysctlbyname("hw.machine", NULL, &namesize, NULL, 0); - machine = malloc(namesize); + machine = reinterpret_cast(malloc(namesize)); sysctlbyname("hw.machine", machine, &namesize, NULL, 0); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, "Device: %s\r\n", machine); } diff --git a/coreapi/ringtoneplayer_ios.h b/coreapi/ringtoneplayer_ios.h index 57a8f6c43..37aa2b22e 100644 --- a/coreapi/ringtoneplayer_ios.h +++ b/coreapi/ringtoneplayer_ios.h @@ -20,8 +20,17 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "linphone/linphonecore.h" +#ifdef __cplusplus +extern "C" { +#endif + LinphoneRingtonePlayer* linphone_ringtoneplayer_ios_new(); void linphone_ringtoneplayer_ios_destroy(LinphoneRingtonePlayer* rp); int linphone_ringtoneplayer_ios_start_with_cb(LinphoneRingtonePlayer* rp, const char* ringtone, int loop_pause_ms, LinphoneRingtonePlayerFunc end_of_ringtone, void * user_data); bool_t linphone_ringtoneplayer_ios_is_started(LinphoneRingtonePlayer* rp); int linphone_ringtoneplayer_ios_stop(LinphoneRingtonePlayer* rp); + +#ifdef __cplusplus +} +#endif + From 2dd2ee6e7911083c094e89b34250edf4d1b16713 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Mon, 31 Jul 2017 16:08:56 +0200 Subject: [PATCH 02/16] fix memory leaks --- coreapi/chat.c | 8 ++++---- coreapi/chat_file_transfer.c | 2 +- coreapi/friendlist.c | 8 ++++---- coreapi/lime.c | 21 ++++++++++++++------- coreapi/linphonecore.c | 9 +++++---- coreapi/presence.c | 28 ++++++++++++++-------------- coreapi/private.h | 4 ++-- coreapi/xml.c | 6 +++--- coreapi/xmlrpc.c | 2 +- tester/message_tester.c | 12 +++++++++--- 10 files changed, 57 insertions(+), 43 deletions(-) diff --git a/coreapi/chat.c b/coreapi/chat.c index 458b8d16f..b7ecd439b 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -784,8 +784,8 @@ static const char *iscomposing_prefix = "/xsi:isComposing"; static void process_im_is_composing_notification(LinphoneChatRoom *cr, xmlparsing_context_t *xml_ctx) { char xpath_str[MAX_XPATH_LENGTH]; xmlXPathObjectPtr iscomposing_object; - const char *state_str = NULL; - const char *refresh_str = NULL; + char *state_str = NULL; + char *refresh_str = NULL; int refresh_duration = lp_config_get_int(cr->lc->config, "sip", "composing_remote_refresh_timeout", COMPOSING_DEFAULT_REMOTE_REFRESH_TIMEOUT); int i; @@ -860,8 +860,8 @@ static void process_imdn(LinphoneChatRoom *cr, xmlparsing_context_t *xml_ctx) { xmlXPathObjectPtr imdn_object; xmlXPathObjectPtr delivery_status_object; xmlXPathObjectPtr display_status_object; - const char *message_id_str = NULL; - const char *datetime_str = NULL; + char *message_id_str = NULL; + char *datetime_str = NULL; LinphoneCore *lc = linphone_chat_room_get_core(cr); LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(lc); diff --git a/coreapi/chat_file_transfer.c b/coreapi/chat_file_transfer.c index c1cfa822e..3d5e69378 100644 --- a/coreapi/chat_file_transfer.c +++ b/coreapi/chat_file_transfer.c @@ -335,7 +335,7 @@ static void linphone_chat_message_process_response_from_post_file(void *data, } else { /* no encryption key, transfer in plain, just copy the msg sent by server */ msg->message = ms_strdup(body); } - msg->content_type = ms_strdup("application/vnd.gsma.rcs-ft-http+xml"); + linphone_chat_message_set_content_type(msg, "application/vnd.gsma.rcs-ft-http+xml"); linphone_chat_message_ref(msg); linphone_chat_message_set_state(msg, LinphoneChatMessageStateFileTransferDone); _release_http_request(msg); diff --git a/coreapi/friendlist.c b/coreapi/friendlist.c index 52dc2ee4d..f29b626ff 100644 --- a/coreapi/friendlist.c +++ b/coreapi/friendlist.c @@ -205,9 +205,9 @@ static void linphone_friend_list_parse_multipart_related_body(LinphoneFriendList LinphoneFriend *lf; LinphoneContent *presence_part; xmlXPathObjectPtr resource_object; - const char *version_str = NULL; - const char *full_state_str = NULL; - const char *uri = NULL; + char *version_str = NULL; + char *full_state_str = NULL; + char *uri = NULL; bool_t full_state = FALSE; int version; int i; @@ -249,7 +249,7 @@ static void linphone_friend_list_parse_multipart_related_body(LinphoneFriendList resource_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, "/rlmi:list/rlmi:resource/rlmi:instance[@state=\"active\"]/.."); if ((resource_object != NULL) && (resource_object->nodesetval != NULL)) { for (i = 1; i <= resource_object->nodesetval->nodeNr; i++) { - const char *cid = NULL; + char *cid = NULL; linphone_xml_xpath_context_set_node(xml_ctx, xmlXPathNodeSetItem(resource_object->nodesetval, i-1)); cid = linphone_get_xml_text_content(xml_ctx, "./rlmi:instance/@cid"); if (cid != NULL) { diff --git a/coreapi/lime.c b/coreapi/lime.c index 08c976402..76c44556e 100644 --- a/coreapi/lime.c +++ b/coreapi/lime.c @@ -579,7 +579,7 @@ int lime_decryptMultipartMessage(void *cachedb, uint8_t *message, const char *se uint8_t selfZid[12]; /* same data but in byte buffer */ char xpath_str[MAX_XPATH_LENGTH]; limeKey_t associatedKey; - const char *peerZidHex = NULL; + char *peerZidHex = NULL; const char *sessionIndexHex = NULL; xmlparsing_context_t *xml_ctx; xmlXPathObjectPtr msg_object; @@ -635,12 +635,14 @@ int lime_decryptMultipartMessage(void *cachedb, uint8_t *message, const char *se msg_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, "/doc/msg"); if ((msg_object != NULL) && (msg_object->nodesetval != NULL)) { for (i = 1; i <= msg_object->nodesetval->nodeNr; i++) { - const char *currentZidHex; - const char *encryptedMessageb64; - const char *encryptedContentTypeb64; + char *currentZidHex; + + char *encryptedMessageb64; + char *encryptedContentTypeb64; snprintf(xpath_str, sizeof(xpath_str), "/doc/msg[%i]/pzid", i); currentZidHex = linphone_get_xml_text_content(xml_ctx, xpath_str); if ((currentZidHex != NULL) && (strcmp(currentZidHex, (char *)selfZidHex) == 0)) { + linphone_free_xml_text_content(currentZidHex); /* We found the msg node we are looking for */ snprintf(xpath_str, sizeof(xpath_str), "/doc/msg[%i]/index", i); sessionIndexHex = linphone_get_xml_text_content(xml_ctx, xpath_str); @@ -665,9 +667,9 @@ int lime_decryptMultipartMessage(void *cachedb, uint8_t *message, const char *se } break; } - if (currentZidHex != NULL) linphone_free_xml_text_content(currentZidHex); } } + if (msg_object != NULL) xmlXPathFreeObject(msg_object); } /* do we have retrieved correctly all the needed data */ @@ -766,6 +768,7 @@ bool_t linphone_chat_room_lime_available(LinphoneChatRoom *cr) { return the list of possible uris and store the selected one in the chatroom ? */ res = (lime_getCachedSndKeysByURI(zrtp_cache_db, &associatedKeys) == 0); lime_freeKeys(&associatedKeys); + ms_free(peer); return res; } } @@ -795,7 +798,10 @@ int lime_im_encryption_engine_process_incoming_message_cb(LinphoneImEncryptionEn } peerUri = linphone_address_as_string_uri_only(msg->from); selfUri = linphone_address_as_string_uri_only(msg->to); - retval = lime_decryptMultipartMessage(zrtp_cache_db, (uint8_t *)msg->message, selfUri, peerUri, &decrypted_body, &decrypted_content_type, bctbx_time_string_to_sec(lp_config_get_string(lc->config, "sip", "lime_key_validity", "0"))); + retval = lime_decryptMultipartMessage(zrtp_cache_db, (uint8_t *)msg->message, selfUri, peerUri, &decrypted_body, &decrypted_content_type, + bctbx_time_string_to_sec(lp_config_get_string(lc->config, "sip", "lime_key_validity", "0"))); + ms_free(peerUri); + ms_free(selfUri); if (retval != 0) { ms_warning("Unable to decrypt message, reason : %s", lime_error_code_to_string(retval)); if (decrypted_body) ms_free(decrypted_body); @@ -809,6 +815,7 @@ int lime_im_encryption_engine_process_incoming_message_cb(LinphoneImEncryptionEn msg->message = (char *)decrypted_body; if (decrypted_content_type != NULL) { linphone_chat_message_set_content_type(msg, decrypted_content_type); + ms_free(decrypted_content_type); } else { if (strcmp("application/cipher.vnd.gsma.rcs-ft-http+xml", msg->content_type) == 0) { linphone_chat_message_set_content_type(msg, "application/vnd.gsma.rcs-ft-http+xml"); @@ -863,7 +870,7 @@ int lime_im_encryption_engine_process_outgoing_message_cb(LinphoneImEncryptionEn ms_free(msg->message); } msg->message = (char *)crypted_body; - msg->content_type = ms_strdup(new_content_type); + linphone_chat_message_set_content_type(msg, new_content_type); } ms_free(peerUri); ms_free(selfUri); diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 327ce21b2..98b6f7e98 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -6614,8 +6614,7 @@ void linphone_core_zrtp_cache_db_init(LinphoneCore *lc, const char *fileName) { int ret; const char *errmsg; const char *backupExtension = "_backup"; - char *backupName = reinterpret_cast(malloc(snprintf(NULL, 0, "%s%s", fileName, backupExtension) + 1)); - sprintf(backupName, "%s%s", fileName, backupExtension); + char *backupName = bctbx_strdup_printf("%s%s", fileName, backupExtension); sqlite3 *db; linphone_core_zrtp_cache_close(lc); @@ -6628,7 +6627,7 @@ void linphone_core_zrtp_cache_db_init(LinphoneCore *lc, const char *fileName) { unlink(backupName); rename(fileName, backupName); lc->zrtp_cache_db=NULL; - return; + goto end; } ret = ms_zrtp_initCache((void *)db); /* this may perform an update, check return value */ @@ -6643,11 +6642,13 @@ void linphone_core_zrtp_cache_db_init(LinphoneCore *lc, const char *fileName) { unlink(backupName); rename(fileName, backupName); lc->zrtp_cache_db = NULL; - return; + goto end; } /* everything ok, set the db pointer into core */ lc->zrtp_cache_db = db; +end: + if (backupName) bctbx_free(backupName); #endif /* SQLITE_STORAGE_ENABLED */ } diff --git a/coreapi/presence.c b/coreapi/presence.c index 45e285b59..a49550991 100644 --- a/coreapi/presence.c +++ b/coreapi/presence.c @@ -1201,8 +1201,8 @@ static int process_pidf_xml_presence_service_notes(xmlparsing_context_t *xml_ctx char xpath_str[MAX_XPATH_LENGTH]; xmlXPathObjectPtr note_object; LinphonePresenceNote *note; - const char *note_str; - const char *lang; + char *note_str; + char *lang; int i; snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/pidf:note", service_prefix, service_idx); @@ -1231,10 +1231,10 @@ static int process_pidf_xml_presence_services(xmlparsing_context_t *xml_ctx, Lin xmlXPathObjectPtr service_object; xmlXPathObjectPtr pidfonline_object; LinphonePresenceService *service; - const char *basic_status_str; - const char *service_id_str; - const char *timestamp_str; - const char *contact_str; + char *basic_status_str; + char *service_id_str; + char *timestamp_str; + char *contact_str; LinphonePresenceBasicStatus basic_status; int i; @@ -1306,7 +1306,7 @@ static int process_pidf_xml_presence_person_activities(xmlparsing_context_t *xml xmlXPathObjectPtr activities_object; xmlNodePtr activity_node; LinphonePresenceActivity *activity; - const char *description; + char *description; int i, j; int err = 0; @@ -1321,7 +1321,7 @@ static int process_pidf_xml_presence_person_activities(xmlparsing_context_t *xml activity_node = activities_object->nodesetval->nodeTab[j]; if ((activity_node->name != NULL) && (is_valid_activity_name((const char *)activity_node->name) == TRUE)) { LinphonePresenceActivityType acttype; - description = (const char *)xmlNodeGetContent(activity_node); + description = (char *)xmlNodeGetContent(activity_node); if ((description != NULL) && (description[0] == '\0')) { linphone_free_xml_text_content(description); description = NULL; @@ -1348,8 +1348,8 @@ static int process_pidf_xml_presence_person_notes(xmlparsing_context_t *xml_ctx, char xpath_str[MAX_XPATH_LENGTH]; xmlXPathObjectPtr note_object; LinphonePresenceNote *note; - const char *note_str; - const char *lang; + char *note_str; + char *lang; int i; snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/rpid:activities/rpid:note", person_prefix, person_idx); @@ -1395,8 +1395,8 @@ static int process_pidf_xml_presence_persons(xmlparsing_context_t *xml_ctx, Linp char xpath_str[MAX_XPATH_LENGTH]; xmlXPathObjectPtr person_object; LinphonePresencePerson *person; - const char *person_id_str; - const char *person_timestamp_str; + char *person_id_str; + char *person_timestamp_str; time_t timestamp; int i; int err = 0; @@ -1444,8 +1444,8 @@ static int process_pidf_xml_presence_notes(xmlparsing_context_t *xml_ctx, Linpho char xpath_str[MAX_XPATH_LENGTH]; xmlXPathObjectPtr note_object; LinphonePresenceNote *note; - const char *note_str; - const char *lang; + char *note_str; + char *lang; int i; note_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, "/pidf:presence/pidf:note"); diff --git a/coreapi/private.h b/coreapi/private.h index 73897d995..b95916c60 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -1700,8 +1700,8 @@ void linphone_xmlparsing_genericxml_error(void *ctx, const char *fmt, ...); int linphone_create_xml_xpath_context(xmlparsing_context_t *xml_ctx); void linphone_xml_xpath_context_set_node(xmlparsing_context_t *xml_ctx, xmlNodePtr node); char * linphone_get_xml_text_content(xmlparsing_context_t *xml_ctx, const char *xpath_expression); -const char * linphone_get_xml_attribute_text_content(xmlparsing_context_t *xml_ctx, const char *xpath_expression, const char *attribute_name); -void linphone_free_xml_text_content(const char *text); +char * linphone_get_xml_attribute_text_content(xmlparsing_context_t *xml_ctx, const char *xpath_expression, const char *attribute_name); +void linphone_free_xml_text_content(char *text); xmlXPathObjectPtr linphone_get_xml_xpath_object_for_node_list(xmlparsing_context_t *xml_ctx, const char *xpath_expression); void linphone_xml_xpath_context_init_carddav_ns(xmlparsing_context_t *xml_ctx); diff --git a/coreapi/xml.c b/coreapi/xml.c index 4e2e154a4..4443b8610 100644 --- a/coreapi/xml.c +++ b/coreapi/xml.c @@ -94,7 +94,7 @@ char * linphone_get_xml_text_content(xmlparsing_context_t *xml_ctx, const char * return (char *)text; } -const char * linphone_get_xml_attribute_text_content(xmlparsing_context_t *xml_ctx, const char *xpath_expression, const char *attribute_name) { +char * linphone_get_xml_attribute_text_content(xmlparsing_context_t *xml_ctx, const char *xpath_expression, const char *attribute_name) { xmlXPathObjectPtr xpath_obj; xmlChar *text = NULL; @@ -118,10 +118,10 @@ const char * linphone_get_xml_attribute_text_content(xmlparsing_context_t *xml_c xmlXPathFreeObject(xpath_obj); } - return (const char *)text; + return (char*)text; } -void linphone_free_xml_text_content(const char *text) { +void linphone_free_xml_text_content(char *text) { xmlFree((xmlChar *)text); } diff --git a/coreapi/xmlrpc.c b/coreapi/xmlrpc.c index 682bb8f24..221876f15 100644 --- a/coreapi/xmlrpc.c +++ b/coreapi/xmlrpc.c @@ -196,7 +196,7 @@ static void parse_valid_xml_rpc_response(LinphoneXmlRpcRequest *request, const c request->status = LinphoneXmlRpcStatusFailed; xml_ctx->doc = xmlReadDoc((const unsigned char*)response_body, 0, NULL, 0); if (xml_ctx->doc != NULL) { - const char *response_str = NULL; + char *response_str = NULL; if (linphone_create_xml_xpath_context(xml_ctx) < 0) goto end; switch (request->response.type) { case LinphoneXmlRpcArgInt: diff --git a/tester/message_tester.c b/tester/message_tester.c index 972dfd58e..d3293b1c7 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -877,6 +877,7 @@ static int enable_lime_for_message_test(LinphoneCoreManager *marie, LinphoneCore int ret = 0; char* paulineUri = NULL; char* marieUri = NULL; + char *tmp; if (!linphone_core_lime_available(marie->lc) || !linphone_core_lime_available(pauline->lc)) { ms_warning("Lime not available, skiping"); @@ -891,8 +892,12 @@ static int enable_lime_for_message_test(LinphoneCoreManager *marie, LinphoneCore lp_config_set_int(pauline->lc->config, "sip", "zrtp_cache_migration_done", TRUE); /* create temporary cache files: setting the database_path will create and initialise the files */ - remove(bc_tester_file("tmpZIDCacheMarie.sqlite")); - remove(bc_tester_file("tmpZIDCachePauline.sqlite")); + tmp = bc_tester_file("tmpZIDCacheMarie.sqlite"); + remove(tmp); + bc_free(tmp); + tmp = bc_tester_file("tmpZIDCachePauline.sqlite"); + remove(tmp); + bc_free(tmp); filepath = bc_tester_file("tmpZIDCacheMarie.sqlite"); linphone_core_set_zrtp_secrets_file(marie->lc, filepath); bc_free(filepath); @@ -920,7 +925,8 @@ static int enable_lime_for_message_test(LinphoneCoreManager *marie, LinphoneCore sqlite3_free(errmsg); return -1; } - + ms_free(paulineUri); + ms_free(marieUri); return 0; #else /* SQLITE_STORAGE_ENABLED */ From 0c98521d5d5e3640ff943e947f9c0bae2e5928e4 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 31 Jul 2017 16:13:29 +0200 Subject: [PATCH 03/16] Add log to notify the start of stun server resolution. --- coreapi/nat_policy.c | 1 + 1 file changed, 1 insertion(+) diff --git a/coreapi/nat_policy.c b/coreapi/nat_policy.c index 229d3a2cd..acc34c34b 100644 --- a/coreapi/nat_policy.c +++ b/coreapi/nat_policy.c @@ -234,6 +234,7 @@ void linphone_nat_policy_resolve_stun_server(LinphoneNatPolicy *policy) { if (service != NULL) { int family = AF_INET; if (linphone_core_ipv6_enabled(policy->lc) == TRUE) family = AF_INET6; + ms_message("Starting stun server resolution [%s]", host); policy->stun_resolver_context = sal_resolve(policy->lc->sal, service, "udp", host, port, family, stun_server_resolved, policy); if (policy->stun_resolver_context) belle_sip_object_ref(policy->stun_resolver_context); } From 2f9760c696b4d79c4c741238f3461b998e63f0b6 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 31 Jul 2017 16:17:49 +0200 Subject: [PATCH 04/16] Fix compilation. --- coreapi/lime.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coreapi/lime.c b/coreapi/lime.c index 76c44556e..ba4e00027 100644 --- a/coreapi/lime.c +++ b/coreapi/lime.c @@ -580,7 +580,7 @@ int lime_decryptMultipartMessage(void *cachedb, uint8_t *message, const char *se char xpath_str[MAX_XPATH_LENGTH]; limeKey_t associatedKey; char *peerZidHex = NULL; - const char *sessionIndexHex = NULL; + char *sessionIndexHex = NULL; xmlparsing_context_t *xml_ctx; xmlXPathObjectPtr msg_object; uint8_t *encryptedMessage = NULL; From 02e20501c98ec2392366cdc49b1cfae6492570f3 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 31 Jul 2017 17:37:42 +0200 Subject: [PATCH 05/16] Improve conditions about STUN being activated or not when gathering ICE candidates. --- coreapi/misc.c | 9 +++------ coreapi/nat_policy.c | 2 +- coreapi/private.h | 1 + 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/coreapi/misc.c b/coreapi/misc.c index bb8541138..6d0f4819f 100644 --- a/coreapi/misc.c +++ b/coreapi/misc.c @@ -616,9 +616,6 @@ int linphone_core_gather_ice_candidates(LinphoneCore *lc, LinphoneCall *call){ IceCheckList *video_cl; IceCheckList *text_cl; LinphoneNatPolicy *nat_policy = call->nat_policy; - const char *server = NULL; - - if (nat_policy != NULL) server = linphone_nat_policy_get_stun_server(nat_policy); if (call->ice_session == NULL) return -1; audio_cl = ice_session_check_list(call->ice_session, call->main_audio_stream_index); @@ -626,7 +623,7 @@ int linphone_core_gather_ice_candidates(LinphoneCore *lc, LinphoneCall *call){ text_cl = ice_session_check_list(call->ice_session, call->main_text_stream_index); if ((audio_cl == NULL) && (video_cl == NULL) && (text_cl == NULL)) return -1; - if ((nat_policy != NULL) && (server != NULL) && (server[0] != '\0')) { + if ((nat_policy != NULL) && linphone_nat_policy_stun_server_activated(nat_policy)) { ai=linphone_nat_policy_get_stun_server_addrinfo(nat_policy); if (ai==NULL){ ms_warning("Fail to resolve STUN server for ICE gathering, continuing without stun."); @@ -658,9 +655,9 @@ int linphone_core_gather_ice_candidates(LinphoneCore *lc, LinphoneCall *call){ } else { linphone_core_add_local_ice_candidates(call, AF_INET, local_addr, audio_cl, video_cl, text_cl); } - if ((ai != NULL) && (nat_policy != NULL) - && (linphone_nat_policy_stun_enabled(nat_policy) || linphone_nat_policy_turn_enabled(nat_policy))) { + if ((ai != NULL) && (nat_policy != NULL) && linphone_nat_policy_stun_server_activated(nat_policy)) { bool_t gathering_in_progress; + const char *server = linphone_nat_policy_get_stun_server(nat_policy); ms_message("ICE: gathering candidate from [%s] using %s", server, linphone_nat_policy_turn_enabled(nat_policy) ? "TURN" : "STUN"); /* Gather local srflx candidates. */ ice_session_enable_turn(call->ice_session, linphone_nat_policy_turn_enabled(nat_policy)); diff --git a/coreapi/nat_policy.c b/coreapi/nat_policy.c index acc34c34b..dcd0b2d75 100644 --- a/coreapi/nat_policy.c +++ b/coreapi/nat_policy.c @@ -45,7 +45,7 @@ static void linphone_nat_policy_destroy(LinphoneNatPolicy *policy) { } } -static bool_t linphone_nat_policy_stun_server_activated(LinphoneNatPolicy *policy) { +bool_t linphone_nat_policy_stun_server_activated(LinphoneNatPolicy *policy) { const char *server = linphone_nat_policy_get_stun_server(policy); return (server != NULL) && (server[0] != '\0') && ((linphone_nat_policy_stun_enabled(policy) == TRUE) || (linphone_nat_policy_turn_enabled(policy) == TRUE)); diff --git a/coreapi/private.h b/coreapi/private.h index b95916c60..bc2175efb 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -1371,6 +1371,7 @@ struct _LinphoneNatPolicy { BELLE_SIP_DECLARE_VPTR_NO_EXPORT(LinphoneNatPolicy); +bool_t linphone_nat_policy_stun_server_activated(LinphoneNatPolicy *policy); void linphone_nat_policy_save_to_config(const LinphoneNatPolicy *policy); struct _LinphoneImNotifPolicy { From a0080315deb30087706926ad3ff093d3c2996526 Mon Sep 17 00:00:00 2001 From: Erwan Croze Date: Tue, 1 Aug 2017 11:30:59 +0200 Subject: [PATCH 06/16] Add info to core.h types.h for wrapper --- include/linphone/core.h | 1 + include/linphone/types.h | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/linphone/core.h b/include/linphone/core.h index 3596e1b8e..6fc0bb029 100644 --- a/include/linphone/core.h +++ b/include/linphone/core.h @@ -3719,6 +3719,7 @@ LINPHONE_PUBLIC void linphone_core_set_device_rotation(LinphoneCore *lc, int rot * This is needed on some mobile platforms to get the number of degrees the camera sensor * is rotated relative to the screen. * @param lc The linphone core related to the operation + * @ingroup media_parameters * @return The camera sensor rotation in degrees (0 to 360) or -1 if it could not be retrieved */ LINPHONE_PUBLIC int linphone_core_get_camera_sensor_rotation(LinphoneCore *lc); diff --git a/include/linphone/types.h b/include/linphone/types.h index 7a10f54ed..177e4c003 100644 --- a/include/linphone/types.h +++ b/include/linphone/types.h @@ -487,8 +487,9 @@ typedef struct belle_sip_dict LinphoneDictionary; /** * Enum describing the result of the echo canceller calibration process. + * @ingroup media_parameters **/ -typedef enum { +typedef enum _LinphoneEcCalibratorStatus { LinphoneEcCalibratorInProgress, /**< The echo canceller calibration process is on going */ LinphoneEcCalibratorDone, /**< The echo canceller calibration has been performed and produced an echo delay measure */ LinphoneEcCalibratorFailed, /**< The echo canceller calibration process has failed */ From 91922bcaab535acc0e8463894010c8ad63c451a6 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Tue, 1 Aug 2017 13:58:39 +0200 Subject: [PATCH 07/16] fix compilation issues on Android. --- coreapi/linphonecore.c | 6 +++--- coreapi/message_storage.c | 5 +---- coreapi/private.h | 1 + 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 98b6f7e98..edb59eefe 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -7003,9 +7003,9 @@ const char * linphone_core_get_video_preset(const LinphoneCore *lc) { static int linphone_core_call_void_method(jobject obj, jmethodID id) { JNIEnv *env=ms_get_jni_env(); if (env && obj) { - (*env)->CallVoidMethod(env,obj,id); - if ((*env)->ExceptionCheck(env)) { - (*env)->ExceptionClear(env); + env->CallVoidMethod(obj,id); + if (env->ExceptionCheck()) { + env->ExceptionClear(); return -1; } else return 0; diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index b064b268e..9067afdd7 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -23,9 +23,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #ifdef SQLITE_STORAGE_ENABLED -#ifndef PRIu64 -#define PRIu64 "I64u" -#endif #ifndef _WIN32 #if !defined(__QNXNTO__) && !defined(__ANDROID__) @@ -883,7 +880,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 %" PRIu64 " microseconds", statement, (uint64_t)(duration / 1000LL) ); + 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/coreapi/private.h b/coreapi/private.h index bc2175efb..33d8b9710 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -47,6 +47,7 @@ #include + #ifdef HAVE_CONFIG_H #include "config.h" #endif From 784f2bb8ef17dad5f8abbed2dfd232033343b1bc Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Tue, 1 Aug 2017 15:59:19 +0200 Subject: [PATCH 08/16] reset bandwidth controller between state and upon network changes --- coreapi/linphonecall.c | 3 +++ coreapi/linphonecore.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 8a93953ae..4471af2f1 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -1721,6 +1721,9 @@ static void linphone_call_set_terminated(LinphoneCall *call){ if (call->chat_room){ call->chat_room->call = NULL; } + if (lc->calls == NULL){ + ms_bandwidth_controller_reset_state(lc->bw_controller); + } } /*function to be called at each incoming reINVITE, in order to adjust various local parameters to what is being offered by remote: diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index edb59eefe..28972673c 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -6218,6 +6218,9 @@ static void set_media_network_reachable(LinphoneCore* lc, bool_t is_media_reacha bctbx_list_for_each(lc->calls, (MSIterateFunc)linphone_call_refresh_sockets); } linphone_core_repair_calls(lc); + if (lc->bw_controller){ + ms_bandwidth_controller_reset_state(lc->bw_controller); + } } } From 42896dce67519eaa99a1f340a6283e70d2378a7a Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 1 Aug 2017 17:13:36 +0200 Subject: [PATCH 09/16] Fix Android wrapper of LinphonePlayer. --- coreapi/linphonecore_jni.cc | 207 +++++++++--------- .../org/linphone/core/LinphonePlayerImpl.java | 2 + 2 files changed, 110 insertions(+), 99 deletions(-) diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index ad6726f24..9d1b3a2c3 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -3512,6 +3512,106 @@ extern "C" jint Java_org_linphone_core_PayloadTypeImpl_getRate(JNIEnv* env,jobj return (jint)payload_type_get_rate(pt); } +/* Linphone Player */ +struct LinphonePlayerData { + LinphonePlayerData(JNIEnv *env, jobject listener, jobject window) : + mListener(env->NewGlobalRef(listener)), + mWindow(env->NewGlobalRef(window)) + { + mListenerClass = (jclass)env->NewGlobalRef(env->GetObjectClass(listener)); + mEndOfFileMethodID = env->GetMethodID(mListenerClass, "endOfFile", "(Lorg/linphone/core/LinphonePlayer;)V"); + if(mEndOfFileMethodID == NULL) { + ms_error("Could not get endOfFile method ID"); + env->ExceptionClear(); + } + } + + ~LinphonePlayerData() { + JNIEnv *env; + jvm->AttachCurrentThread(&env, NULL); + env->DeleteGlobalRef(mListener); + env->DeleteGlobalRef(mListenerClass); + if (mWindow) env->DeleteGlobalRef(mWindow); + } + + void setPlayer(jobject player) { mJLinphonePlayer = player; } + + jobject mListener; + jclass mListenerClass; + jobject mJLinphonePlayer; + jobject mWindow; + jmethodID mEndOfFileMethodID; +}; + +static void _eof_callback(LinphonePlayer *player, void *user_data) { + JNIEnv *env; + LinphonePlayerData *player_data = (LinphonePlayerData *)user_data; + jvm->AttachCurrentThread(&env, NULL); + env->CallVoidMethod(player_data->mListener, player_data->mEndOfFileMethodID, player_data->mJLinphonePlayer); +} + +extern "C" void Java_org_linphone_core_LinphonePlayerImpl_init(JNIEnv *env, jobject jPlayer, jlong ptr) { + LinphonePlayer *player = (LinphonePlayer *)ptr; + LinphonePlayerData *data = (LinphonePlayerData *)linphone_player_get_user_data(player); + data->setPlayer(jPlayer); +} + +extern "C" jint Java_org_linphone_core_LinphonePlayerImpl_open(JNIEnv *env, jobject jPlayer, jlong ptr, jstring filename) { + const char *cfilename = GetStringUTFChars(env, filename); + if(linphone_player_open((LinphonePlayer *)ptr, cfilename) == -1) { + ReleaseStringUTFChars(env, filename, cfilename); + return -1; + } + ReleaseStringUTFChars(env, filename, cfilename); + return 0; +} + +extern "C" jint Java_org_linphone_core_LinphonePlayerImpl_start(JNIEnv *env, jobject jobj, jlong ptr) { + return (jint)linphone_player_start((LinphonePlayer *)ptr); +} + +extern "C" jint Java_org_linphone_core_LinphonePlayerImpl_pause(JNIEnv *env, jobject jobj, jlong ptr) { + return (jint)linphone_player_pause((LinphonePlayer *)ptr); +} + +extern "C" jint Java_org_linphone_core_LinphonePlayerImpl_seek(JNIEnv *env, jobject jobj, jlong ptr, jint timeMs) { + return (jint)linphone_player_seek((LinphonePlayer *)ptr, timeMs); +} + +extern "C" jint Java_org_linphone_core_LinphonePlayerImpl_getState(JNIEnv *env, jobject jobj, jlong ptr) { + return (jint)linphone_player_get_state((LinphonePlayer *)ptr); +} + +extern "C" jint Java_org_linphone_core_LinphonePlayerImpl_getDuration(JNIEnv *env, jobject jobj, jlong ptr) { + return (jint)linphone_player_get_duration((LinphonePlayer *)ptr); +} + +extern "C" jint Java_org_linphone_core_LinphonePlayerImpl_getCurrentPosition(JNIEnv *env, jobject jobj, jlong ptr) { + return (jint)linphone_player_get_current_position((LinphonePlayer *)ptr); +} + +extern "C" void Java_org_linphone_core_LinphonePlayerImpl_close(JNIEnv *env, jobject playerPtr, jlong ptr) { + LinphonePlayer *player = (LinphonePlayer *)ptr; + if(player->user_data) { + LinphonePlayerData *data = (LinphonePlayerData *)player->user_data; + if(data) delete data; + player->user_data = NULL; + } + linphone_player_close(player); +} + +extern "C" void Java_org_linphone_core_LinphonePlayerImpl_destroy(JNIEnv *env, jobject jobj, jlong playerPtr) { + LinphonePlayer *player = (LinphonePlayer *)playerPtr; + if(player == NULL) { + ms_error("Cannot destroy the LinphonePlayerImpl object. Native pointer is NULL"); + return; + } + if(linphone_player_get_user_data(player)) { + delete (LinphonePlayerData *)linphone_player_get_user_data(player); + } + linphone_player_unref(player); +} + //LinphoneCall extern "C" void Java_org_linphone_core_LinphoneCallImpl_finalize(JNIEnv* env ,jobject thiz @@ -3702,7 +3802,10 @@ extern "C" jfloat Java_org_linphone_core_LinphoneCallImpl_getAverageQuality( JNI } extern "C" jlong Java_org_linphone_core_LinphoneCallImpl_getPlayer(JNIEnv *env, jobject thiz, jlong callPtr) { - return (jlong)linphone_call_get_player((LinphoneCall *)callPtr); + LinphonePlayer *player = linphone_call_get_player((LinphoneCall *)callPtr); + LinphonePlayerData *data = new LinphonePlayerData(env, thiz, NULL); + linphone_player_set_user_data(player, data); + return (jlong)linphone_player_ref(player); } extern "C" jboolean Java_org_linphone_core_LinphoneCallImpl_mediaInProgress( JNIEnv* env @@ -7503,109 +7606,15 @@ JNIEXPORT void JNICALL Java_org_linphone_core_ErrorInfoImpl_unref(JNIEnv *env, j } #endif -/* Linphone Player */ -struct LinphonePlayerData { - LinphonePlayerData(JNIEnv *env, jobject listener, jobject jLinphonePlayer) : - mListener(env->NewGlobalRef(listener)), - mJLinphonePlayer(env->NewGlobalRef(jLinphonePlayer)) - { - mListenerClass = (jclass)env->NewGlobalRef(env->GetObjectClass(listener)); - mEndOfFileMethodID = env->GetMethodID(mListenerClass, "endOfFile", "(Lorg/linphone/core/LinphonePlayer;)V"); - if(mEndOfFileMethodID == NULL) { - ms_error("Could not get endOfFile method ID"); - env->ExceptionClear(); - } - } - - ~LinphonePlayerData() { - JNIEnv *env; - jvm->AttachCurrentThread(&env, NULL); - env->DeleteGlobalRef(mListener); - env->DeleteGlobalRef(mListenerClass); - env->DeleteGlobalRef(mJLinphonePlayer); - } - - jobject mListener; - jclass mListenerClass; - jobject mJLinphonePlayer; - jmethodID mEndOfFileMethodID; -}; - -static void _eof_callback(LinphonePlayer *player, void *user_data) { - JNIEnv *env; - LinphonePlayerData *player_data = (LinphonePlayerData *)user_data; - jvm->AttachCurrentThread(&env, NULL); - env->CallVoidMethod(player_data->mListener, player_data->mEndOfFileMethodID, player_data->mJLinphonePlayer); -} - -extern "C" jint Java_org_linphone_core_LinphonePlayerImpl_open(JNIEnv *env, jobject jPlayer, jlong ptr, jstring filename) { - const char *cfilename = GetStringUTFChars(env, filename); - if(linphone_player_open((LinphonePlayer *)ptr, cfilename) == -1) { - ReleaseStringUTFChars(env, filename, cfilename); - return -1; - } - ReleaseStringUTFChars(env, filename, cfilename); - return 0; -} - -extern "C" jint Java_org_linphone_core_LinphonePlayerImpl_start(JNIEnv *env, jobject jobj, jlong ptr) { - return (jint)linphone_player_start((LinphonePlayer *)ptr); -} - -extern "C" jint Java_org_linphone_core_LinphonePlayerImpl_pause(JNIEnv *env, jobject jobj, jlong ptr) { - return (jint)linphone_player_pause((LinphonePlayer *)ptr); -} - -extern "C" jint Java_org_linphone_core_LinphonePlayerImpl_seek(JNIEnv *env, jobject jobj, jlong ptr, jint timeMs) { - return (jint)linphone_player_seek((LinphonePlayer *)ptr, timeMs); -} - -extern "C" jint Java_org_linphone_core_LinphonePlayerImpl_getState(JNIEnv *env, jobject jobj, jlong ptr) { - return (jint)linphone_player_get_state((LinphonePlayer *)ptr); -} - -extern "C" jint Java_org_linphone_core_LinphonePlayerImpl_getDuration(JNIEnv *env, jobject jobj, jlong ptr) { - return (jint)linphone_player_get_duration((LinphonePlayer *)ptr); -} - -extern "C" jint Java_org_linphone_core_LinphonePlayerImpl_getCurrentPosition(JNIEnv *env, jobject jobj, jlong ptr) { - return (jint)linphone_player_get_current_position((LinphonePlayer *)ptr); -} - -extern "C" void Java_org_linphone_core_LinphonePlayerImpl_close(JNIEnv *env, jobject playerPtr, jlong ptr) { - LinphonePlayer *player = (LinphonePlayer *)ptr; - if(player->user_data) { - LinphonePlayerData *data = (LinphonePlayerData *)player->user_data; - if(data) delete data; - player->user_data = NULL; - } - linphone_player_close(player); -} - -extern "C" void Java_org_linphone_core_LinphonePlayerImpl_destroy(JNIEnv *env, jobject jobj, jlong playerPtr) { - LinphonePlayer *player = (LinphonePlayer *)playerPtr; - if(player == NULL) { - ms_error("Cannot destroy the LinphonePlayerImpl object. Native pointer is NULL"); - return; - } - if(player->user_data) { - delete (LinphonePlayerData *)player->user_data; - } - jobject window_id = (jobject)ms_media_player_get_window_id((MSMediaPlayer *)player->impl); - if(window_id) env->DeleteGlobalRef(window_id); - _linphone_player_destroy(player); -} - extern "C" jlong Java_org_linphone_core_LinphoneCoreImpl_createLocalPlayer(JNIEnv *env, jobject jobj, jlong ptr, jobject window) { - jobject window_ref = NULL; - window_ref = env->NewGlobalRef(window); - LinphonePlayer *player = linphone_core_create_local_player((LinphoneCore *)ptr, NULL, "MSAndroidDisplay", (void *)window_ref); + LinphonePlayer *player = linphone_core_create_local_player((LinphoneCore *)ptr, NULL, "MSAndroidDisplay", (void *)window); + LinphonePlayerData *data = new LinphonePlayerData(env, jobj, window); + linphone_player_set_user_data(player, data); if(player == NULL) { ms_error("Fails to create a player"); - if(window_ref) env->DeleteGlobalRef(window_ref); return 0; } else { - return (jlong)player; + return (jlong)linphone_player_ref(player); } } diff --git a/java/impl/org/linphone/core/LinphonePlayerImpl.java b/java/impl/org/linphone/core/LinphonePlayerImpl.java index cbb162843..2ef445eb3 100644 --- a/java/impl/org/linphone/core/LinphonePlayerImpl.java +++ b/java/impl/org/linphone/core/LinphonePlayerImpl.java @@ -10,8 +10,10 @@ package org.linphone.core; public class LinphonePlayerImpl implements LinphonePlayer { private long nativePtr = 0; + private native void init(long nativePtr); LinphonePlayerImpl(long nativePtr) { this.nativePtr = nativePtr; + init(nativePtr); } private native int open(long nativePtr, String filename); From 0af1581533fe5f89a198a2e6a8a5bfa9a72fb974 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 2 Aug 2017 11:12:20 +0200 Subject: [PATCH 10/16] Fix build for Android. --- coreapi/linphonecore_jni.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index 9d1b3a2c3..08108902b 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -3543,9 +3543,9 @@ struct LinphonePlayerData { jmethodID mEndOfFileMethodID; }; -static void _eof_callback(LinphonePlayer *player, void *user_data) { +static void _eof_callback(LinphonePlayer *player) { JNIEnv *env; - LinphonePlayerData *player_data = (LinphonePlayerData *)user_data; + LinphonePlayerData *player_data = (LinphonePlayerData *)linphone_player_get_user_data(player); jvm->AttachCurrentThread(&env, NULL); env->CallVoidMethod(player_data->mListener, player_data->mEndOfFileMethodID, player_data->mJLinphonePlayer); } @@ -3554,6 +3554,7 @@ extern "C" void Java_org_linphone_core_LinphonePlayerImpl_init(JNIEnv *env, jobj LinphonePlayer *player = (LinphonePlayer *)ptr; LinphonePlayerData *data = (LinphonePlayerData *)linphone_player_get_user_data(player); data->setPlayer(jPlayer); + linphone_player_cbs_set_eof_reached(linphone_player_get_callbacks(player), _eof_callback); } extern "C" jint Java_org_linphone_core_LinphonePlayerImpl_open(JNIEnv *env, jobject jPlayer, jlong ptr, jstring filename) { From 44de032c86f31b2d2e0cdb54a6358c13a7bc08c2 Mon Sep 17 00:00:00 2001 From: Ronan Abhamon Date: Mon, 24 Jul 2017 16:53:28 +0200 Subject: [PATCH 11/16] feat(core): add a Cpim parser --- CMakeLists.txt | 5 +- coreapi/CMakeLists.txt | 21 +- src/cpim/cpim.h | 26 ++ src/cpim/header/cpim-core-headers.cpp | 105 ++++++ src/cpim/header/cpim-core-headers.h | 111 +++++++ src/cpim/header/cpim-generic-header.cpp | 117 +++++++ src/cpim/header/cpim-generic-header.h | 65 ++++ src/cpim/header/cpim-header-p.h | 40 +++ src/cpim/header/cpim-header.cpp | 45 +++ src/cpim/header/cpim-header.h | 55 ++++ src/cpim/message/cpim-message.cpp | 140 ++++++++ src/cpim/message/cpim-message.h | 61 ++++ src/cpim/parser/cpim-grammar.cpp | 207 ++++++++++++ src/cpim/parser/cpim-grammar.h | 30 ++ src/cpim/parser/cpim-parser.cpp | 413 ++++++++++++++++++++++++ src/cpim/parser/cpim-parser.h | 82 +++++ src/object/object.h | 5 +- src/object/singleton.h | 14 +- src/utils/utils.cpp | 49 +++ src/utils/utils.h | 39 +++ tester/CMakeLists.txt | 13 +- tester/call_multi_tester.c | 32 +- tester/cpim_tester.cpp | 379 ++++++++++++++++++++++ tester/liblinphone_tester.h | 36 ++- tester/tester.c | 1 + 25 files changed, 2043 insertions(+), 48 deletions(-) create mode 100644 src/cpim/cpim.h create mode 100644 src/cpim/header/cpim-core-headers.cpp create mode 100644 src/cpim/header/cpim-core-headers.h create mode 100644 src/cpim/header/cpim-generic-header.cpp create mode 100644 src/cpim/header/cpim-generic-header.h create mode 100644 src/cpim/header/cpim-header-p.h create mode 100644 src/cpim/header/cpim-header.cpp create mode 100644 src/cpim/header/cpim-header.h create mode 100644 src/cpim/message/cpim-message.cpp create mode 100644 src/cpim/message/cpim-message.h create mode 100644 src/cpim/parser/cpim-grammar.cpp create mode 100644 src/cpim/parser/cpim-grammar.h create mode 100644 src/cpim/parser/cpim-parser.cpp create mode 100644 src/cpim/parser/cpim-parser.h create mode 100644 src/utils/utils.cpp create mode 100644 src/utils/utils.h create mode 100644 tester/cpim_tester.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e4ac40ba1..8d0ff54e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,11 +115,13 @@ if(LINPHONE_BUILDER_GROUP_EXTERNAL_SOURCE_PATH_BUILDERS) include("${EP_ortp_CONFIG_DIR}/ORTPConfig.cmake") set(BcToolbox_FIND_COMPONENTS tester) include("${EP_bctoolbox_CONFIG_DIR}/BcToolboxConfig.cmake") + include("${EP_belr_CONFIG_DIR}/BelrConfig.cmake") else() find_package(BelleSIP REQUIRED) find_package(Mediastreamer2 REQUIRED) find_package(ORTP REQUIRED) find_package(BcToolbox 0.0.3 REQUIRED OPTIONAL_COMPONENTS tester) + find_package(Belr REQUIRED) endif() find_package(XML2 REQUIRED) find_package(Zlib) @@ -208,6 +210,7 @@ set(LINPHONE_INCLUDE_DIRS ${BELLESIP_INCLUDE_DIRS} ${MEDIASTREAMER2_INCLUDE_DIRS} ${BCTOOLBOX_CORE_INCLUDE_DIRS} + ${BELR_INCLUDE_DIRS} ) if (BZRTP_FOUND) list(APPEND LINPHONE_INCLUDE_DIRS ${BZRTP_INCLUDE_DIRS}) @@ -243,7 +246,7 @@ if(MSVC) endif() add_definitions("-DLINPHONE_EXPORTS") -set(LINPHONE_CPPFLAGS ${BELCARD_CPPFLAGS} ${BELLESIP_CPPFLAGS} ${MEDIASTREAMER2_CPPFLAGS} ${BCTOOLBOX_CPPFLAGS}) +set(LINPHONE_CPPFLAGS ${BELCARD_CPPFLAGS} ${BELLESIP_CPPFLAGS} ${MEDIASTREAMER2_CPPFLAGS} ${BCTOOLBOX_CPPFLAGS} ${BELR_CPPFLAGS}) if(ENABLE_STATIC) list(APPEND LINPHONE_CPPFLAGS "-DLINPHONE_STATIC") endif() diff --git a/coreapi/CMakeLists.txt b/coreapi/CMakeLists.txt index 4dd144b9d..03cf08acb 100644 --- a/coreapi/CMakeLists.txt +++ b/coreapi/CMakeLists.txt @@ -35,9 +35,18 @@ endif() set(LINPHONE_PRIVATE_HEADER_FILES + ../src/cpim/cpim.h + ../src/cpim/header/cpim-core-headers.h + ../src/cpim/header/cpim-generic-header.h + ../src/cpim/header/cpim-header-p.h + ../src/cpim/header/cpim-header.h + ../src/cpim/message/cpim-message.h + ../src/cpim/parser/cpim-grammar.h + ../src/cpim/parser/cpim-parser.h ../src/object/object.h ../src/object/singleton.h ../src/utils/general.h + ../src/utils/utils.h bellesip_sal/sal_impl.h carddav.h conference_private.h @@ -122,7 +131,16 @@ set(LINPHONE_SOURCE_FILES_C xmlrpc.c vtables.c ) -set(LINPHONE_SOURCE_FILES_CXX conference.cc) +set(LINPHONE_SOURCE_FILES_CXX + ../src/cpim/header/cpim-core-headers.cpp + ../src/cpim/header/cpim-generic-header.cpp + ../src/cpim/header/cpim-header.cpp + ../src/cpim/message/cpim-message.cpp + ../src/cpim/parser/cpim-grammar.cpp + ../src/cpim/parser/cpim-parser.cpp + ../src/utils/utils.cpp + conference.cc +) if(ANDROID) list(APPEND LINPHONE_SOURCE_FILES_CXX linphonecore_jni.cc) set_source_files_properties(linphonecore_jni.cc PROPERTIES COMPILE_DEFINITIONS "USE_JAVAH") @@ -158,6 +176,7 @@ set(LIBS ${MEDIASTREAMER2_LIBRARIES} ${ORTP_LIBRARIES} ${XML2_LIBRARIES} + ${BELR_LIBRARIES} ) if(WIN32 AND NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") list(APPEND LIBS "Ws2_32") diff --git a/src/cpim/cpim.h b/src/cpim/cpim.h new file mode 100644 index 000000000..a2aa0a824 --- /dev/null +++ b/src/cpim/cpim.h @@ -0,0 +1,26 @@ +/* + * cpim.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _CPIM_H_ +#define _CPIM_H_ + +#include "message/cpim-message.h" + +// ============================================================================= + +#endif // ifndef _CPIM_H_ diff --git a/src/cpim/header/cpim-core-headers.cpp b/src/cpim/header/cpim-core-headers.cpp new file mode 100644 index 000000000..d49ba6ee0 --- /dev/null +++ b/src/cpim/header/cpim-core-headers.cpp @@ -0,0 +1,105 @@ +/* + * cpim-core-headers.cpp + * Copyright (C) 2017 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 3 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, see . + */ + +#include "cpim-header-p.h" +#include "cpim/parser/cpim-parser.h" + +#include "cpim-core-headers.h" + +using namespace std; + +using namespace LinphonePrivate; + +// ============================================================================= + +Cpim::CoreHeader::CoreHeader () : Header(*new HeaderPrivate) {} + +Cpim::CoreHeader::CoreHeader (HeaderPrivate &p) : Header(p) {} + +Cpim::CoreHeader::~CoreHeader () {} + +bool Cpim::CoreHeader::isValid () const { + return !getValue().empty(); +} + +// ----------------------------------------------------------------------------- + +#define MAKE_CORE_HEADER_IMPL(CLASS_PREFIX) \ + bool Cpim::CLASS_PREFIX ## Header::setValue(const string &value) { \ + return Parser::getInstance()->coreHeaderIsValid(value) && Header::setValue(value); \ + } + +MAKE_CORE_HEADER_IMPL(From); +MAKE_CORE_HEADER_IMPL(To); +MAKE_CORE_HEADER_IMPL(Cc); +MAKE_CORE_HEADER_IMPL(DateTime); + +MAKE_CORE_HEADER_IMPL(Ns); +MAKE_CORE_HEADER_IMPL(Require); + +#undef MAKE_CORE_HEADER_IMPL + +// ----------------------------------------------------------------------------- + +void Cpim::CoreHeader::force (const std::string &value) { + Header::setValue(value); +} + +// ----------------------------------------------------------------------------- + +class Cpim::SubjectHeaderPrivate : public HeaderPrivate { +public: + string language; +}; + +Cpim::SubjectHeader::SubjectHeader () : CoreHeader(*new SubjectHeaderPrivate) {} + +bool Cpim::SubjectHeader::setValue (const string &value) { + return Parser::getInstance()->coreHeaderIsValid(value) && Header::setValue(value); +} + +string Cpim::SubjectHeader::getLanguage () const { + L_D(const SubjectHeader); + return d->language; +} + +bool Cpim::SubjectHeader::setLanguage (const string &language) { + if (!language.empty() && !Parser::getInstance()->subjectHeaderLanguageIsValid(language)) + return false; + + L_D(SubjectHeader); + d->language = language; + + return true; +} + +string Cpim::SubjectHeader::asString () const { + L_D(const SubjectHeader); + + string languageParam; + if (!d->language.empty()) + languageParam = ";lang=" + d->language; + + return getName() + ":" + languageParam + " " + getValue() + "\r\n"; +} + +void Cpim::SubjectHeader::force (const string &value, const string &language) { + L_D(SubjectHeader); + CoreHeader::force(value); + d->language = language; +} diff --git a/src/cpim/header/cpim-core-headers.h b/src/cpim/header/cpim-core-headers.h new file mode 100644 index 000000000..965904908 --- /dev/null +++ b/src/cpim/header/cpim-core-headers.h @@ -0,0 +1,111 @@ +/* + * cpim-core-headers.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _CPIM_CORE_HEADERS_H_ +#define _CPIM_CORE_HEADERS_H_ + +#include "cpim-header.h" + +// ============================================================================= + +#define MAKE_CORE_HEADER(CLASS_PREFIX, NAME) \ + class CLASS_PREFIX ## Header : public CoreHeader { \ + public: \ + CLASS_PREFIX ## Header() = default; \ + inline std::string getName() const override { \ + return NAME; \ + } \ + bool setValue(const std::string &value) override; \ + private: \ + L_DISABLE_COPY(CLASS_PREFIX ## Header); \ + }; + +namespace LinphonePrivate { + namespace Cpim { + class HeaderNode; + + // ------------------------------------------------------------------------- + // Generic core header. + // ------------------------------------------------------------------------- + + class CoreHeader : public Header { + friend class HeaderNode; + + public: + CoreHeader (); + + virtual ~CoreHeader () = 0; + + bool isValid () const override; + + protected: + explicit CoreHeader (HeaderPrivate &p); + + void force (const std::string &value); + + private: + L_DISABLE_COPY(CoreHeader); + }; + + // ------------------------------------------------------------------------- + // Core headers. + // ------------------------------------------------------------------------- + + MAKE_CORE_HEADER(From, "From"); + MAKE_CORE_HEADER(To, "To"); + MAKE_CORE_HEADER(Cc, "cc"); + MAKE_CORE_HEADER(DateTime, "DateTime"); + MAKE_CORE_HEADER(Ns, "NS"); + MAKE_CORE_HEADER(Require, "Require"); + + // ------------------------------------------------------------------------- + // Specific Subject declaration. + // ------------------------------------------------------------------------- + + class SubjectHeaderPrivate; + + class SubjectHeader : public CoreHeader { + friend class HeaderNode; + + public: + SubjectHeader (); + + inline std::string getName () const override { + return "Subject"; + } + + bool setValue (const std::string &value) override; + + std::string getLanguage () const; + bool setLanguage (const std::string &language); + + std::string asString () const override; + + protected: + void force (const std::string &value, const std::string &language); + + private: + L_DECLARE_PRIVATE(SubjectHeader); + L_DISABLE_COPY(SubjectHeader); + }; + } +} + +#undef MAKE_CORE_HEADER + +#endif // ifndef _CPIM_CORE_HEADERS_H_ diff --git a/src/cpim/header/cpim-generic-header.cpp b/src/cpim/header/cpim-generic-header.cpp new file mode 100644 index 000000000..178cfbb27 --- /dev/null +++ b/src/cpim/header/cpim-generic-header.cpp @@ -0,0 +1,117 @@ +/* + * cpim-generic-header.cpp + * Copyright (C) 2017 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 3 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, see . + */ + +#include + +#include "cpim-header-p.h" +#include "cpim/parser/cpim-parser.h" +#include "utils/utils.h" + +#include "cpim-generic-header.h" + +using namespace std; + +using namespace LinphonePrivate; + +// ============================================================================= + +class Cpim::GenericHeaderPrivate : public HeaderPrivate { +public: + string name; + shared_ptr > > parameters = make_shared > >(); +}; + +Cpim::GenericHeader::GenericHeader () : Header(*new GenericHeaderPrivate) {} + +string Cpim::GenericHeader::getName () const { + L_D(const GenericHeader); + return d->name; +} + +bool Cpim::GenericHeader::setName (const string &name) { + L_D(GenericHeader); + + static const set reserved = { + "From", "To", "cc", "DateTime", "Subject", "NS", "Require" + }; + + if ( + reserved.find(name) != reserved.end() || + !Parser::getInstance()->headerNameIsValid(name) + ) + return false; + + d->name = name; + return true; +} + +bool Cpim::GenericHeader::setValue (const string &value) { + return Parser::getInstance()->headerValueIsValid(value) && Header::setValue(value); +} + +Cpim::GenericHeader::ParameterList Cpim::GenericHeader::getParameters () const { + L_D(const GenericHeader); + return d->parameters; +} + +bool Cpim::GenericHeader::addParameter (const string &key, const string &value) { + L_D(GenericHeader); + + if (!Parser::getInstance()->headerParameterIsValid(key + "=" + value)) + return false; + + d->parameters->push_back(make_pair(key, value)); + return true; +} + +void Cpim::GenericHeader::removeParameter (const string &key, const string &value) { + L_D(GenericHeader); + d->parameters->remove(make_pair(key, value)); +} + +bool Cpim::GenericHeader::isValid () const { + L_D(const GenericHeader); + return !d->name.empty() && !getValue().empty(); +} + +string Cpim::GenericHeader::asString () const { + L_D(const GenericHeader); + + string parameters; + for (const auto ¶meter : *d->parameters) + parameters += ";" + parameter.first + "=" + parameter.second; + + return d->name + ":" + parameters + " " + getValue() + "\r\n"; +} + +// ----------------------------------------------------------------------------- + +void Cpim::GenericHeader::force (const string &name, const string &value, const string ¶meters) { + L_D(GenericHeader); + + // Set name/value. + d->name = name; + Header::setValue(value); + + // Parse and build parameters list. + for (const auto ¶meter : Utils::split(parameters, ';')) { + size_t equalIndex = parameter.find('='); + if (equalIndex != string::npos) + d->parameters->push_back(make_pair(parameter.substr(0, equalIndex), parameter.substr(equalIndex + 1))); + } +} diff --git a/src/cpim/header/cpim-generic-header.h b/src/cpim/header/cpim-generic-header.h new file mode 100644 index 000000000..fb1a198cf --- /dev/null +++ b/src/cpim/header/cpim-generic-header.h @@ -0,0 +1,65 @@ +/* + * cpim-generic-header.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _CPIM_GENERIC_HEADER_H_ +#define _CPIM_GENERIC_HEADER_H_ + +#include +#include + +#include "cpim-header.h" + +// ============================================================================= + +namespace LinphonePrivate { + namespace Cpim { + class GenericHeaderPrivate; + class HeaderNode; + + class GenericHeader : public Header { + friend class HeaderNode; + + public: + GenericHeader (); + + std::string getName () const override; + bool setName (const std::string &name); + + bool setValue (const std::string &value) override; + + typedef std::shared_ptr > > ParameterList; + + ParameterList getParameters () const; + bool addParameter (const std::string &key, const std::string &value); + void removeParameter (const std::string &key, const std::string &value); + + bool isValid () const override; + + std::string asString () const override; + + protected: + void force (const std::string &name, const std::string &value, const std::string ¶meters); + + private: + L_DECLARE_PRIVATE(GenericHeader); + L_DISABLE_COPY(GenericHeader); + }; + } +} + +#endif // ifndef _CPIM_GENERIC_HEADER_H_ diff --git a/src/cpim/header/cpim-header-p.h b/src/cpim/header/cpim-header-p.h new file mode 100644 index 000000000..2e3208dd4 --- /dev/null +++ b/src/cpim/header/cpim-header-p.h @@ -0,0 +1,40 @@ +/* + * cpim-header-p.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _CPIM_HEADER_P_H_ +#define _CPIM_HEADER_P_H_ + +#include "cpim-header.h" + +// ============================================================================= + +namespace LinphonePrivate { + namespace Cpim { + class HeaderPrivate : public ObjectPrivate { + public: + virtual ~HeaderPrivate () = default; + + private: + std::string value; + + L_DECLARE_PUBLIC(Header); + }; + } +} + +#endif // ifndef _CPIM_HEADER_P_H_ diff --git a/src/cpim/header/cpim-header.cpp b/src/cpim/header/cpim-header.cpp new file mode 100644 index 000000000..694b9ffd1 --- /dev/null +++ b/src/cpim/header/cpim-header.cpp @@ -0,0 +1,45 @@ +/* + * cpim-header.cpp + * Copyright (C) 2017 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 3 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, see . + */ + +#include "cpim-header-p.h" + +#include "cpim-header.h" + +using namespace std; + +using namespace LinphonePrivate; + +// ============================================================================= + +Cpim::Header::Header (HeaderPrivate &p) : Object(p) {} + +string Cpim::Header::getValue () const { + L_D(const Header); + return d->value; +} + +bool Cpim::Header::setValue (const string &value) { + L_D(Header); + d->value = value; + return true; +} + +string Cpim::Header::asString () const { + L_D(const Header); + return getName() + ": " + d->value + "\r\n"; +} diff --git a/src/cpim/header/cpim-header.h b/src/cpim/header/cpim-header.h new file mode 100644 index 000000000..3dbcb2fbe --- /dev/null +++ b/src/cpim/header/cpim-header.h @@ -0,0 +1,55 @@ +/* + * cpim-header.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _CPIM_HEADER_H_ +#define _CPIM_HEADER_H_ + +#include + +#include "object/object.h" + +// ============================================================================= + +namespace LinphonePrivate { + namespace Cpim { + class HeaderPrivate; + + class Header : public Object { + public: + virtual ~Header () = default; + + virtual std::string getName () const = 0; + + std::string getValue () const; + virtual bool setValue (const std::string &value); + + virtual bool isValid () const = 0; + + virtual std::string asString () const; + + protected: + explicit Header (HeaderPrivate &p); + + private: + L_DECLARE_PRIVATE(Header); + L_DISABLE_COPY(Header); + }; + } +} + +#endif // ifndef _CPIM_HEADER_H_ diff --git a/src/cpim/message/cpim-message.cpp b/src/cpim/message/cpim-message.cpp new file mode 100644 index 000000000..af1e06209 --- /dev/null +++ b/src/cpim/message/cpim-message.cpp @@ -0,0 +1,140 @@ +/* + * cpim-message.cpp + * Copyright (C) 2017 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 3 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, see . + */ + +#include + +#include "cpim/parser/cpim-parser.h" +#include "utils/utils.h" + +#include "cpim-message.h" + +using namespace std; + +using namespace LinphonePrivate; + +// ============================================================================= + +class Cpim::MessagePrivate : public ObjectPrivate { +public: + typedef list > PrivHeaderList; + + shared_ptr cpimHeaders = make_shared(); + shared_ptr messageHeaders = make_shared(); + string content; +}; + +Cpim::Message::Message () : Object(*new MessagePrivate) {} + +// ----------------------------------------------------------------------------- + +Cpim::Message::HeaderList Cpim::Message::getCpimHeaders () const { + L_D(const Message); + return d->cpimHeaders; +} + +bool Cpim::Message::addCpimHeader (const Header &cpimHeader) { + L_D(Message); + + if (!cpimHeader.isValid()) + return false; + + d->cpimHeaders->push_back(Parser::getInstance()->cloneHeader(cpimHeader)); + return true; +} + +void Cpim::Message::removeCpimHeader (const Header &cpimHeader) { + L_D(Message); + d->cpimHeaders->remove_if([&cpimHeader](const shared_ptr &header) { + return cpimHeader.getName() == header->getName() && cpimHeader.getValue() == header->getValue(); + }); +} + +// ----------------------------------------------------------------------------- + +Cpim::Message::HeaderList Cpim::Message::getMessageHeaders () const { + L_D(const Message); + return d->messageHeaders; +} + +bool Cpim::Message::addMessageHeader (const Header &messageHeader) { + L_D(Message); + + if (!messageHeader.isValid()) + return false; + + d->messageHeaders->push_back(Parser::getInstance()->cloneHeader(messageHeader)); + return true; +} + +void Cpim::Message::removeMessageHeader (const Header &messageHeader) { + L_D(Message); + d->messageHeaders->remove_if([&messageHeader](const shared_ptr &header) { + return messageHeader.getName() == header->getName() && messageHeader.getValue() == header->getValue(); + }); +} + +// ----------------------------------------------------------------------------- + +string Cpim::Message::getContent () const { + L_D(const Message); + return d->content; +} + +bool Cpim::Message::setContent (const string &content) { + L_D(Message); + d->content = content; + return true; +} + +// ----------------------------------------------------------------------------- + +bool Cpim::Message::isValid () const { + L_D(const Message); + + return find_if(d->cpimHeaders->cbegin(), d->cpimHeaders->cend(), + [](const shared_ptr &header) { + return Utils::iequals(header->getName(), "content-type") && header->getValue() == "Message/CPIM"; + }) != d->cpimHeaders->cend(); +} + +// ----------------------------------------------------------------------------- + +string Cpim::Message::asString () const { + L_D(const Message); + + string output; + for (const auto &cpimHeader : *d->cpimHeaders) + output += cpimHeader->asString(); + + output += "\r\n"; + + for (const auto &messageHeader : *d->messageHeaders) + output += messageHeader->asString(); + + output += "\r\n"; + output += ""; // TODO: Headers MIME. + output += getContent(); + + return output; +} + +// ----------------------------------------------------------------------------- + +shared_ptr Cpim::Message::createFromString (const string &str) { + return Parser::getInstance()->parseMessage(str); +} diff --git a/src/cpim/message/cpim-message.h b/src/cpim/message/cpim-message.h new file mode 100644 index 000000000..2620fe336 --- /dev/null +++ b/src/cpim/message/cpim-message.h @@ -0,0 +1,61 @@ +/* + * cpim-message.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _CPIM_MESSAGE_H_ +#define _CPIM_MESSAGE_H_ + +#include "cpim/header/cpim-core-headers.h" +#include "cpim/header/cpim-generic-header.h" + +// ============================================================================= + +namespace LinphonePrivate { + namespace Cpim { + class MessagePrivate; + + class Message : public Object { + public: + Message (); + + typedef std::shared_ptr > > HeaderList; + + HeaderList getCpimHeaders () const; + bool addCpimHeader (const Header &cpimHeader); + void removeCpimHeader (const Header &cpimHeader); + + HeaderList getMessageHeaders () const; + bool addMessageHeader (const Header &messageHeader); + void removeMessageHeader (const Header &messageHeader); + + std::string getContent () const; + bool setContent (const std::string &content); + + bool isValid () const; + + std::string asString () const; + + static std::shared_ptr createFromString (const std::string &str); + + private: + L_DECLARE_PRIVATE(Message); + L_DISABLE_COPY(Message); + }; + } +} + +#endif // ifndef _CPIM_MESSAGE_H_ diff --git a/src/cpim/parser/cpim-grammar.cpp b/src/cpim/parser/cpim-grammar.cpp new file mode 100644 index 000000000..91eab3d30 --- /dev/null +++ b/src/cpim/parser/cpim-grammar.cpp @@ -0,0 +1,207 @@ +/* + * cpim-grammar.cpp + * Copyright (C) 2017 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 3 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, see . + */ + +#include "cpim-grammar.h" + +// ============================================================================= + +namespace LinphonePrivate { + static const char *grammar = +// See: https://tools.ietf.org/html/rfc3862 +R"==GRAMMAR==( +Message = Headers CRLF Headers CRLF + +Headers = *Header +Header = Header-name ":" Header-parameters SP Header-value CRLF + +Header-name = [ Name-prefix "." ] Name +Name-prefix = Name + +Header-parameters = *( ";" Parameter ) + +Parameter = Lang-param / Ext-param +Lang-param = "lang=" Language-tag +Ext-param = Param-name "=" Param-value +Param-name = Name +Param-value = Token / Number / String + +Header-value = *HEADERCHAR + +From-header = %d70.114.111.109 ": " From-header-value +From-header-value = [ Formal-name ] "<" URI ">" + +To-header = %d84.111 ": " To-header-value +To-header-value = [ Formal-name ] "<" URI ">" + +DateTime-header = %d68.97.116.101.84.105.109.101 ": " DateTime-header-value +DateTime-header-value = date-time + +cc-header = %d99.99 ": " cc-header-value +cc-header-value = [ Formal-name ] "<" URI ">" + +Subject-header = %d83.117.98.106.101.99.116 ":" Subject-header-value +Subject-header-value = [ ";" Lang-param ] SP *HEADERCHAR + +NS-header = %d78.83 ": " NS-header-value +NS-header-value = [ Name-prefix SP ] "<" URI ">" + +Require-header = %d82.101.113.117.105.114.101 ": " Require-header-value +Require-header-value = Header-name *( "," Header-name ) + +Name = 1*NAMECHAR +Token = 1*TOKENCHAR +Number = 1*DIGIT +String = DQUOTE *( Str-char / Escape ) DQUOTE +Str-char = %x20-21 / %x23-5B / %x5D-7E / UCS-high +Escape = "\" ( "u" 4(HEXDIG) / "b" / "t" / "n" / "r" / DQUOTE / "'" / "\" ) + +Formal-name = 1*( Token SP ) / String + +HEADERCHAR = UCS-no-CTL / Escape + +NAMECHAR = %x21 / %x23-27 / %x2a-2b / %x2d / %x5e-60 + / %x7c / %x7e / ALPHA / DIGIT + +TOKENCHAR = NAMECHAR / "." / UCS-high + +UCS-no-CTL = UTF8-no-CTL +UCS-high = UTF8-multi +UTF8-no-CTL = %x20-7e / UTF8-multi +UTF8-multi = %xC0-DF %x80-BF + / %xE0-EF %x80-BF %x80-BF + / %xF0-F7 %x80-BF %x80-BF %x80-BF + / %xF8-FB %x80-BF %x80-BF %x80-BF %x80-BF + / %xFC-FD %x80-BF %x80-BF %x80-BF %x80-BF %x80-BF +)==GRAMMAR==" + +// See: https://tools.ietf.org/html/rfc2396 +R"==GRAMMAR==( +URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + +hier-part = "//" authority path-abempty + / path-absolute + / path-rootless + / path-empty + +URI-reference = URI / relative-ref + +absolute-URI = scheme ":" hier-part [ "?" query ] + +relative-ref = relative-part [ "?" query ] [ "#" fragment ] + +relative-part = "//" authority path-abempty + / path-absolute + / path-noscheme + / path-empty + +scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + +authority = [ userinfo "@" ] host [ ":" port ] +userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) +host = IP-literal / IPv4address / reg-name +port = *DIGIT + +IP-literal = "[" ( IPv6address / IPvFuture ) "]" + +IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) + +IPv6address = 6( h16 ":" ) ls32 + / "::" 5( h16 ":" ) ls32 + / [ h16 ] "::" 4( h16 ":" ) ls32 + / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + / [ *4( h16 ":" ) h16 ] "::" ls32 + / [ *5( h16 ":" ) h16 ] "::" h16 + / [ *6( h16 ":" ) h16 ] "::" + +h16 = 1*4HEXDIG +ls32 = ( h16 ":" h16 ) / IPv4address +IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet +dec-octet = DIGIT + / %x31-39 DIGIT + / "1" 2DIGIT + / "2" %x30-34 DIGIT + / "25" %x30-35 + +reg-name = *( unreserved / pct-encoded / sub-delims ) + +path = path-abempty + / path-absolute + / path-noscheme + / path-rootless + / path-empty + +path-abempty = *( "/" segment ) +path-absolute = "/" [ segment-nz *( "/" segment ) ] +path-noscheme = segment-nz-nc *( "/" segment ) +path-rootless = segment-nz *( "/" segment ) +path-empty = [pchar] + +segment = *pchar +segment-nz = 1*pchar +segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) + +pchar = unreserved / pct-encoded / sub-delims / ":" / "@" / "\," + +query = *( pchar / "/" / "?" ) + +fragment = *( pchar / "/" / "?" ) + +pct-encoded = "%" HEXDIG HEXDIG + +unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +reserved = gen-delims / sub-delims +gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" +sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + / "*" / "+" / "," / ";" / "=" +)==GRAMMAR==" + +// See: https://tools.ietf.org/html/rfc3066 +R"==GRAMMAR==( +Language-Tag = Primary-subtag *( "-" Subtag ) +Primary-subtag = 1*8ALPHA +Subtag = 1*8(ALPHA / DIGIT) +)==GRAMMAR==" + +// See: https://tools.ietf.org/html/rfc3339 +R"==GRAMMAR==( +date-fullyear = 4DIGIT +date-month = 2DIGIT +date-mday = 2DIGIT + +time-hour = 2DIGIT +time-minute = 2DIGIT +time-second = 2DIGIT + +time-secfrac = "." 1*DIGIT +time-numoffset = ( "+" / "-" ) time-hour ":" time-minute +time-offset = "Z" / time-numoffset + +partial-time = time-hour ":" time-minute ":" time-second [ time-secfrac ] + +full-date = date-fullyear "-" date-month "-" date-mday +full-time = partial-time time-offset + +date-time = full-date "T" full-time +)==GRAMMAR=="; +} + +const char *LinphonePrivate::Cpim::getGrammar () { + return grammar; +} diff --git a/src/cpim/parser/cpim-grammar.h b/src/cpim/parser/cpim-grammar.h new file mode 100644 index 000000000..45887a661 --- /dev/null +++ b/src/cpim/parser/cpim-grammar.h @@ -0,0 +1,30 @@ +/* + * cpim-grammar.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _CPIM_GRAMMAR_H_ +#define _CPIM_GRAMMAR_H_ + +// ============================================================================= + +namespace LinphonePrivate { + namespace Cpim { + const char *getGrammar (); + } +} + +#endif // ifndef _CPIM_GRAMMAR_H_ diff --git a/src/cpim/parser/cpim-parser.cpp b/src/cpim/parser/cpim-parser.cpp new file mode 100644 index 000000000..1235b0896 --- /dev/null +++ b/src/cpim/parser/cpim-parser.cpp @@ -0,0 +1,413 @@ +/* + * cpim-parser.cpp + * Copyright (C) 2017 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 3 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, see . + */ + +#include + +#include +#include + +#include "linphone/core.h" + +#include "cpim-grammar.h" +#include "utils/utils.h" + +#include "cpim-parser.h" + +using namespace std; + +using namespace LinphonePrivate; + +// ============================================================================= + +namespace LinphonePrivate { + namespace Cpim { + class Node { + public: + virtual ~Node () = default; + }; + + class HeaderNode : public Node { + public: + HeaderNode () = default; + + explicit HeaderNode (const Header &header) { + mName = header.getName(); + mValue = header.getValue(); + + // Generic header. + const GenericHeader *genericHeader = dynamic_cast(&header); + if (genericHeader) { + for (const auto ¶meter : *genericHeader->getParameters()) + mParameters += ";" + parameter.first + "=" + parameter.second; + return; + } + + // Subject header. + const SubjectHeader *subjectHeader = dynamic_cast(&header); + if (subjectHeader) { + const string language = subjectHeader->getLanguage(); + if (!language.empty()) + mParameters = ";lang=" + language; + } + } + + string getName () const { + return mName; + } + + void setName (const string &name) { + mName = name; + } + + string getParameters () const { + return mParameters; + } + + void setParameters (const string ¶meters) { + mParameters = parameters; + } + + string getValue () const { + return mValue; + } + + void setValue (const string &value) { + mValue = value; + } + + shared_ptr
createHeader (bool force) const; + + private: + template + shared_ptr
createCoreHeader (bool force) const { + shared_ptr header = make_shared(); + if (force) + header->force(mValue); + else if (!header->setValue(mValue)) { + ms_fatal("Unable to set value on core header: `%s` => `%s`.", mName.c_str(), mValue.c_str()); + return nullptr; + } + + return header; + } + + string mValue; + string mName; + string mParameters; + }; + + template<> + shared_ptr
HeaderNode::createCoreHeader(bool force) const { + shared_ptr header = make_shared(); + const string language = mParameters.length() >= 6 ? mParameters.substr(6) : ""; + + if (force) + header->force(mValue, language); + else if (!header->setValue(mValue) || (!language.empty() && !header->setLanguage(language))) { + ms_fatal("Unable to set value on subject header: `%s` => `%s`, `%s`.", mName.c_str(), mValue.c_str(), language.c_str()); + return nullptr; + } + + return header; + } + + shared_ptr
HeaderNode::createHeader (bool force = false) const { + static const unordered_map(HeaderNode::*)(bool)const> reservedHandlers = { + { "From", &HeaderNode::createCoreHeader }, + { "To", &HeaderNode::createCoreHeader }, + { "cc", &HeaderNode::createCoreHeader }, + { "DateTime", &HeaderNode::createCoreHeader }, + { "Subject", &HeaderNode::createCoreHeader }, + { "NS", &HeaderNode::createCoreHeader }, + { "Require", &HeaderNode::createCoreHeader } + }; + + // Core Header. + const auto it = reservedHandlers.find(mName); + if (it != reservedHandlers.cend()) + return (this->*it->second)(force); + + // Generic Header + shared_ptr genericHeader = make_shared(); + genericHeader->force(mName, mValue, mParameters); + return genericHeader; + } + + // ------------------------------------------------------------------------- + + class ListHeaderNode : + public Node, + public list > {}; + + // ------------------------------------------------------------------------- + + class MessageNode : public Node { + public: + void addHeaders (const shared_ptr &headers) { + mHeaders->push_back(headers); + } + + // Warning: Call this function one time! + shared_ptr createMessage () const { + size_t size = mHeaders->size(); + if (size != 2) { + ms_fatal("Bad headers lists size."); + return nullptr; + } + + const shared_ptr message = make_shared(); + const shared_ptr cpimHeaders = mHeaders->front(); + + if (find_if(cpimHeaders->cbegin(), cpimHeaders->cend(), + [](const shared_ptr &headerNode) { + return Utils::iequals(headerNode->getName(), "content-type") && headerNode->getValue() == "Message/CPIM"; + }) == cpimHeaders->cend()) { + ms_fatal("No MIME `Content-Type` found!"); + return nullptr; + } + + // Add MIME headers. + for (const auto &headerNode : *cpimHeaders) { + const shared_ptr header = headerNode->createHeader(); + if (!header || !message->addCpimHeader(*header)) + return nullptr; + } + + // Add message headers. + for (const auto &headerNode : *mHeaders->back()) { + const shared_ptr header = headerNode->createHeader(); + if (!header || !message->addMessageHeader(*header)) + return nullptr; + } + + return message; + } + + private: + shared_ptr > > mHeaders = make_shared > >(); + }; + } +} + +// ----------------------------------------------------------------------------- + +class Cpim::ParserPrivate : public ObjectPrivate { +public: + shared_ptr grammar; +}; + +Cpim::Parser::Parser () : Singleton(*new ParserPrivate) { + L_D(Parser); + + belr::ABNFGrammarBuilder builder; + + d->grammar = builder.createFromAbnf(getGrammar(), make_shared()); + if (!d->grammar) + ms_fatal("Unable to build CPIM grammar."); +} + +// ----------------------------------------------------------------------------- + +shared_ptr Cpim::Parser::parseMessage (const string &input) { + L_D(Parser); + + typedef void (list >::*pushPtr)(const shared_ptr &value); + + belr::Parser > parser(d->grammar); + parser.setHandler( + "Message", belr::make_fn(make_shared ) + )->setCollector( + "Headers", belr::make_sfn(&MessageNode::addHeaders) + ); + + parser.setHandler( + "Headers", belr::make_fn(make_shared ) + )->setCollector( + "Header", belr::make_sfn(static_cast(&ListHeaderNode::push_back)) + ); + + parser.setHandler( + "Header", belr::make_fn(make_shared ) + )->setCollector( + "Header-name", belr::make_sfn(&HeaderNode::setName) + )->setCollector( + "Header-value", belr::make_sfn(&HeaderNode::setValue) + )->setCollector( + "Header-parameters", belr::make_sfn(&HeaderNode::setParameters) + ); + + size_t parsedSize; + shared_ptr node = parser.parseInput("Message", input, &parsedSize); + if (!node) { + ms_fatal("Unable to parse message."); + return nullptr; + } + + shared_ptr messageNode = dynamic_pointer_cast(node); + if (!messageNode) { + ms_fatal("Unable to cast belr result to message node."); + return nullptr; + } + + shared_ptr message = messageNode->createMessage(); + if (message) + message->setContent(input.substr(parsedSize)); + return message; +} + +// ----------------------------------------------------------------------------- + +shared_ptr Cpim::Parser::cloneHeader (const Header &header) { + return HeaderNode(header).createHeader(true); +} + +// ----------------------------------------------------------------------------- + +class EmptyObject {}; + +inline bool headerIsValid (const shared_ptr &grammar, const string &input) { + belr::Parser > parser(grammar); + parser.setHandler( + "Header", belr::make_fn(make_shared ) + ); + + size_t parsedSize; + shared_ptr node = parser.parseInput("Header", input, &parsedSize); + return node && parsedSize == input.length(); +} + +bool Cpim::Parser::headerNameIsValid (const string &headerName) const { + L_D(const Parser); + return ::headerIsValid(d->grammar, headerName + ": value\r\n"); +} + +bool Cpim::Parser::headerValueIsValid (const string &headerValue) const { + L_D(const Parser); + return ::headerIsValid(d->grammar, "key: " + headerValue + "\r\n"); +} + +bool Cpim::Parser::headerParameterIsValid (const std::string &headerParameter) const { + L_D(const Parser); + return ::headerIsValid(d->grammar, "key:;" + headerParameter + " value\r\n"); +} + +// ----------------------------------------------------------------------------- + +inline bool coreHeaderIsValid ( + const shared_ptr &grammar, + const string &headerName, + const string &headerValue, + const string &headerParams = string() +) { + const string mainRule = headerName + "-header"; + + belr::Parser > parser(grammar); + parser.setHandler( + mainRule, belr::make_fn(make_shared ) + ); + + const string input = headerName + ":" + headerParams + " " + headerValue; + + size_t parsedSize; + shared_ptr node = parser.parseInput(mainRule, input, &parsedSize); + return node && parsedSize == input.length(); +} + +template<> +bool Cpim::Parser::coreHeaderIsValid(const string &headerValue) const { + L_D(const Parser); + return ::coreHeaderIsValid(d->grammar, "From", headerValue); +} + +template<> +bool Cpim::Parser::coreHeaderIsValid(const string &headerValue) const { + L_D(const Parser); + return ::coreHeaderIsValid(d->grammar, "To", headerValue); +} + +template<> +bool Cpim::Parser::coreHeaderIsValid(const string &headerValue) const { + L_D(const Parser); + return ::coreHeaderIsValid(d->grammar, "cc", headerValue); +} + +template<> +bool Cpim::Parser::coreHeaderIsValid(const string &headerValue) const { + static const int daysInMonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + + L_D(const Parser); + if (!::coreHeaderIsValid(d->grammar, "DateTime", headerValue)) + return false; + + // Check date. + const int year = stoi(headerValue.substr(0, 4)); + const bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; + + const int month = stoi(headerValue.substr(5, 2)); + if (month < 1 || month > 12) + return false; + + const int day = stoi(headerValue.substr(8, 2)); + if (day < 1 || (month == 2 && isLeapYear ? day > 29 : day > daysInMonth[month - 1])) + return false; + + // Check time. + if ( + stoi(headerValue.substr(11, 2)) > 24 || + stoi(headerValue.substr(14, 2)) > 59 || + stoi(headerValue.substr(17, 2)) > 60 + ) + return false; + + // Check num offset. + if (headerValue.back() != 'Z') { + size_t length = headerValue.length(); + if ( + stoi(headerValue.substr(length - 5, 2)) > 24 || + stoi(headerValue.substr(length - 2, 2)) > 59 + ) + return false; + } + + return true; +} + +template<> +bool Cpim::Parser::coreHeaderIsValid(const string &headerValue) const { + L_D(const Parser); + return ::coreHeaderIsValid(d->grammar, "Subject", headerValue); +} + +template<> +bool Cpim::Parser::coreHeaderIsValid(const string &headerValue) const { + L_D(const Parser); + return ::coreHeaderIsValid(d->grammar, "NS", headerValue); +} + +template<> +bool Cpim::Parser::coreHeaderIsValid(const string &headerValue) const { + L_D(const Parser); + return ::coreHeaderIsValid(d->grammar, "Require", headerValue); +} + +// ----------------------------------------------------------------------------- + +bool Cpim::Parser::subjectHeaderLanguageIsValid (const string &language) const { + L_D(const Parser); + return ::coreHeaderIsValid(d->grammar, "Subject", "SubjectValue", ";lang=" + language); +} diff --git a/src/cpim/parser/cpim-parser.h b/src/cpim/parser/cpim-parser.h new file mode 100644 index 000000000..397e9e3b7 --- /dev/null +++ b/src/cpim/parser/cpim-parser.h @@ -0,0 +1,82 @@ +/* + * cpim-parser.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _CPIM_PARSER_H_ +#define _CPIM_PARSER_H_ + +#include "cpim/message/cpim-message.h" +#include "object/singleton.h" + +// ============================================================================= + +namespace LinphonePrivate { + namespace Cpim { + class ParserPrivate; + + class Parser : public Singleton { + friend class Singleton; + + public: + std::shared_ptr parseMessage (const std::string &input); + + std::shared_ptr
cloneHeader (const Header &header); + + bool headerNameIsValid (const std::string &headerName) const; + bool headerValueIsValid (const std::string &headerValue) const; + bool headerParameterIsValid (const std::string &headerParameter) const; + + template + bool coreHeaderIsValid (const std::string &headerValue) const { + return false; + } + + bool subjectHeaderLanguageIsValid (const std::string &language) const; + + private: + Parser (); + + L_DECLARE_PRIVATE(Parser); + L_DISABLE_COPY(Parser); + }; + + // ------------------------------------------------------------------------- + + template<> + bool Parser::coreHeaderIsValid(const std::string &headerValue) const; + + template<> + bool Parser::coreHeaderIsValid(const std::string &headerValue) const; + + template<> + bool Parser::coreHeaderIsValid(const std::string &headerValue) const; + + template<> + bool Parser::coreHeaderIsValid(const std::string &headerValue) const; + + template<> + bool Parser::coreHeaderIsValid(const std::string &headerValue) const; + + template<> + bool Parser::coreHeaderIsValid(const std::string &headerValue) const; + + template<> + bool Parser::coreHeaderIsValid(const std::string &headerValue) const; + } +} + +#endif // ifndef _CPIM_PARSER_H_ diff --git a/src/object/object.h b/src/object/object.h index 9b387e804..76ae2d67f 100644 --- a/src/object/object.h +++ b/src/object/object.h @@ -44,9 +44,8 @@ namespace LinphonePrivate { } protected: - explicit Object (ObjectPrivate *objectPrivate) : mPrivate(objectPrivate) { - if (mPrivate) - mPrivate->mPublic = this; + explicit Object (ObjectPrivate &p) : mPrivate(&p) { + mPrivate->mPublic = this; } ObjectPrivate *mPrivate = nullptr; diff --git a/src/object/singleton.h b/src/object/singleton.h index 5123e0e73..6acad3c6d 100644 --- a/src/object/singleton.h +++ b/src/object/singleton.h @@ -27,25 +27,25 @@ namespace LinphonePrivate { template class Singleton : public Object { public: - static Singleton *getInstance () { + virtual ~Singleton () = default; + + static T *getInstance () { if (!mInstance) - mInstance = new Singleton(); + mInstance = new T(); return mInstance; } - virtual ~Singleton () = default; - protected: - explicit Singleton (ObjectPrivate *objectPrivate = nullptr) : Object(objectPrivate) {} + explicit Singleton (ObjectPrivate &p) : Object(p) {} private: - static Singleton *mInstance; + static T *mInstance; L_DISABLE_COPY(Singleton); }; template - Singleton *Singleton::mInstance = nullptr; + T *Singleton::mInstance = nullptr; } #endif // ifndef _SINGLETON_H_ diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp new file mode 100644 index 000000000..9e32a883a --- /dev/null +++ b/src/utils/utils.cpp @@ -0,0 +1,49 @@ +/* + * utils.cpp + * Copyright (C) 2017 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 3 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, see . + */ + +#include "utils.h" + +using namespace std; + +using namespace LinphonePrivate; + +// ============================================================================= + +bool Utils::iequals (const string &a, const string &b) { + size_t size = a.size(); + if (b.size() != size) + return false; + + for (size_t i = 0; i < size; ++i) { + if (tolower(a[i]) != tolower(b[i])) + return false; + } + + return true; +} + +vector Utils::split (const string &str, const string &delimiter) { + vector out; + + size_t pos = 0, oldPos = 0; + for (; (pos = str.find(delimiter, pos)) != string::npos; oldPos = pos + 1, pos = oldPos) + out.push_back(str.substr(oldPos, pos - oldPos)); + out.push_back(str.substr(oldPos)); + + return out; +} diff --git a/src/utils/utils.h b/src/utils/utils.h new file mode 100644 index 000000000..1fc468add --- /dev/null +++ b/src/utils/utils.h @@ -0,0 +1,39 @@ +/* + * utils.h + * Copyright (C) 2017 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 3 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, see . + */ + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#include +#include + +// ============================================================================= + +namespace LinphonePrivate { + namespace Utils { + bool iequals (const std::string &a, const std::string &b); + + std::vector split (const std::string &str, const std::string &delimiter); + + inline std::vector split (const std::string &str, char delimiter) { + return split(str, std::string(1, delimiter)); + } + } +} + +#endif // ifndef _UTILS_H_ diff --git a/tester/CMakeLists.txt b/tester/CMakeLists.txt index 45e83bb6c..099c37c2b 100644 --- a/tester/CMakeLists.txt +++ b/tester/CMakeLists.txt @@ -192,6 +192,10 @@ set(SOURCE_FILES_C video_tester.c ) +set(SOURCE_FILES_CXX + cpim_tester.cpp +) + set(SOURCE_FILES_OBJC ) if(APPLE) if (IOS) @@ -200,6 +204,7 @@ if(APPLE) endif() bc_apply_compile_flags(SOURCE_FILES_C STRICT_OPTIONS_CPP STRICT_OPTIONS_C) +bc_apply_compile_flags(SOURCE_FILES_C_CXX STRICT_OPTIONS_CPP STRICT_OPTIONS_CXX) bc_apply_compile_flags(SOURCE_FILES_OBJC STRICT_OPTIONS_CPP STRICT_OPTIONS_OBJC) if(MSVC) @@ -222,7 +227,7 @@ endif() # on mobile platforms, we compile the tester as a library so that we can link with it directly from native applications if(ANDROID OR IOS) - add_library(linphonetester SHARED ${SOURCE_FILES_C}) + add_library(linphonetester SHARED ${SOURCE_FILES_C} ${SOURCE_FILES_CXX}) target_include_directories(linphonetester PUBLIC ${BCTOOLBOX_TESTER_INCLUDE_DIRS}) target_link_libraries(linphonetester ${LINPHONE_LIBS_FOR_TOOLS} ${OTHER_LIBS_FOR_TESTER}) if(IOS) @@ -243,7 +248,7 @@ if(ANDROID OR IOS) PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ ) elseif(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") - add_library(linphone_tester_static STATIC ${SOURCE_FILES_C}) + add_library(linphone_tester_static STATIC ${SOURCE_FILES_C} ${SOURCE_FILES_CXX}) target_include_directories(linphone_tester_static PUBLIC ${BCTOOLBOX_TESTER_INCLUDE_DIRS}) target_link_libraries(linphone_tester_static ${LINPHONE_LIBS_FOR_TOOLS} ${OTHER_LIBS_FOR_TESTER}) @@ -277,9 +282,9 @@ endif() if (NOT ANDROID AND NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") if(IOS) set_source_files_properties(${IOS_RESOURCES_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - add_executable(liblinphone_tester MACOSX_BUNDLE ${IOS_RESOURCES_FILES} ${SOURCE_FILES_C} ${SOURCE_FILES_OBJC}) + add_executable(liblinphone_tester MACOSX_BUNDLE ${IOS_RESOURCES_FILES} ${SOURCE_FILES_C} ${SOURCE_FILES_CXX} ${SOURCE_FILES_OBJC}) else() - add_executable(liblinphone_tester ${SOURCE_FILES_C} ${SOURCE_FILES_OBJC}) + add_executable(liblinphone_tester ${SOURCE_FILES_C} ${SOURCE_FILES_CXX} ${SOURCE_FILES_OBJC}) endif() set_target_properties(liblinphone_tester PROPERTIES LINK_FLAGS "${LINPHONE_LDFLAGS}") set_target_properties(liblinphone_tester PROPERTIES LINKER_LANGUAGE CXX) diff --git a/tester/call_multi_tester.c b/tester/call_multi_tester.c index 2b6ac3cbc..11c0718a5 100644 --- a/tester/call_multi_tester.c +++ b/tester/call_multi_tester.c @@ -254,7 +254,7 @@ static void simple_conference_base(LinphoneCoreManager* marie, LinphoneCoreManag bool_t is_remote_conf; bool_t focus_is_up = (focus && ((LinphoneConferenceServer *)focus)->reg_state == LinphoneRegistrationOk); bctbx_list_t* lcs=bctbx_list_append(NULL,marie->lc); - + lcs=bctbx_list_append(lcs,pauline->lc); lcs=bctbx_list_append(lcs,laure->lc); if (focus) lcs=bctbx_list_append(lcs,focus->lc); @@ -263,7 +263,7 @@ static void simple_conference_base(LinphoneCoreManager* marie, LinphoneCoreManag if(is_remote_conf) BC_ASSERT_PTR_NOT_NULL(focus); if (!BC_ASSERT_TRUE(call(marie,pauline))) goto end; - + marie_call_pauline=linphone_core_get_current_call(marie->lc); pauline_called_by_marie=linphone_core_get_current_call(pauline->lc); BC_ASSERT_TRUE(pause_call_1(marie,marie_call_pauline,pauline,pauline_called_by_marie)); @@ -391,40 +391,40 @@ static void simple_conference_from_scratch(void){ LinphoneCall *pauline_call, *laure_call; bctbx_list_t *participants = NULL; bctbx_list_t *lcs = NULL; - + lcs = bctbx_list_append(lcs, marie->lc); lcs = bctbx_list_append(lcs, pauline->lc); lcs = bctbx_list_append(lcs, laure->lc); - + /*marie creates the conference*/ conf_params = linphone_core_create_conference_params(marie->lc); linphone_conference_params_enable_video(conf_params, FALSE); conf = linphone_core_create_conference_with_params(marie->lc, conf_params); linphone_conference_params_unref(conf_params); - + participants = bctbx_list_append(participants, pauline->identity); participants = bctbx_list_append(participants, laure->identity); - + linphone_conference_invite_participants(conf, participants, NULL); - + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallOutgoingProgress,2,2000)); BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallIncomingReceived,1,10000)); BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallIncomingReceived,1,10000)); - + pauline_call = linphone_core_get_current_call(pauline->lc); laure_call = linphone_core_get_current_call(laure->lc); - + BC_ASSERT_PTR_NOT_NULL(pauline_call); BC_ASSERT_PTR_NOT_NULL(laure_call); - + if (pauline_call && laure_call){ const bctbx_list_t *marie_calls, *it; linphone_call_accept(pauline_call); linphone_call_accept(laure_call); - + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallConnected,2,10000)); BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallStreamsRunning,2,3000)); - + /*make sure that the two calls from Marie's standpoint are in conference*/ marie_calls = linphone_core_get_calls(marie->lc); BC_ASSERT_EQUAL((int)bctbx_list_size(marie_calls), 2, int, "%i"); @@ -433,12 +433,12 @@ static void simple_conference_from_scratch(void){ } /*wait a bit for the conference audio processing to run, despite we do not test it for the moment*/ wait_for_list(lcs,NULL,0,5000); - + linphone_core_terminate_conference(marie->lc); BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallEnd,2,5000)); BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallEnd,1,10000)); BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallEnd,1,10000)); - + BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallReleased,2,1000)); BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallReleased,1,1000)); BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallReleased,1,1000)); @@ -447,7 +447,7 @@ static void simple_conference_from_scratch(void){ linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); linphone_core_manager_destroy(laure); - + bctbx_list_free(participants); bctbx_list_free(lcs); } @@ -691,7 +691,7 @@ static void call_transfer_existing_call(bool_t outgoing_call) { goto end; } } - + marie_call_laure=linphone_core_get_current_call(marie->lc); laure_called_by_marie=linphone_core_get_current_call(laure->lc); diff --git a/tester/cpim_tester.cpp b/tester/cpim_tester.cpp new file mode 100644 index 000000000..27df3690c --- /dev/null +++ b/tester/cpim_tester.cpp @@ -0,0 +1,379 @@ +/* + * liblinphone_tester - liblinphone test suite + * Copyright (C) 2017 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 3 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, see . + */ + +#include "cpim/cpim.h" + +#include "liblinphone_tester.h" + +using namespace std; + +using namespace LinphonePrivate; + +// ============================================================================= + +static void parse_minimal_message (void) { + const string str = "Content-type: Message/CPIM\r\n" + "\r\n" + "Content-Type: text/plain; charset=utf-8\r\n" + "\r\n"; + + shared_ptr message = Cpim::Message::createFromString(str); + if (!BC_ASSERT_PTR_NOT_NULL(message)) return; + + const string str2 = message->asString(); + BC_ASSERT_STRING_EQUAL(str2.c_str(), str.c_str()); +} + +static void set_generic_header_name (void) { + const list > entries = { + { "toto", true }, + { "george.abitbol", true }, + { "tata/titi", false }, + { "hey ho", false }, + { " fail", false }, + { "fail2 ", false }, + + // Reserved. + { "From", false }, + { "To", false }, + { "cc", false }, + { "DateTime", false }, + { "Subject", false }, + { "NS", false }, + { "Require", false }, + + // Case sensitivity. + { "FROM", true }, + { "to", true }, + { "cC", true }, + { "Datetime", true }, + { "SuBject", true }, + { "nS", true }, + { "requirE", true } + }; + + for (const auto &entry : entries) { + Cpim::GenericHeader genericHeader; + + const bool result = genericHeader.setName(entry.first); + BC_ASSERT_EQUAL(result, entry.second, bool, "%d"); + + const string name = genericHeader.getName(); + + if (result) + BC_ASSERT_STRING_EQUAL(name.c_str(), entry.first.c_str()); + else + BC_ASSERT_STRING_EQUAL(name.c_str(), ""); + } +} + +static void set_generic_header_value (void) { + const list > entries = { + { "MyFeatures ", true }, + { "2000-12-13T13:40:00-08:00", true }, + { "2000-12-13T13:40:00-08:00", true }, + { "text/xml; charset=utf-8", true }, + { "text/xml; charset=ut\r\nf-8", false } + }; + + for (const auto &entry : entries) { + Cpim::GenericHeader genericHeader; + + const bool result = genericHeader.setValue(entry.first); + BC_ASSERT_EQUAL(result, entry.second, bool, "%d"); + + const string value = genericHeader.getValue(); + + if (result) + BC_ASSERT_STRING_EQUAL(value.c_str(), entry.first.c_str()); + else + BC_ASSERT_STRING_EQUAL(value.c_str(), ""); + } +} + +static void check_core_header_names (void) { + const list, string> > entries = { + { make_shared(), "From" }, + { make_shared(), "To" }, + { make_shared(), "cc" }, + { make_shared(), "DateTime" }, + { make_shared(), "Subject" }, + { make_shared(), "NS" }, + { make_shared(), "Require" } + }; + + for (const auto &entry : entries) { + const string name = entry.first->getName(); + BC_ASSERT_STRING_EQUAL(name.c_str(), entry.second.c_str()); + } +} + +static void set_core_header_values (void) { + const list, list > > > entries = { + { make_shared(), { + { "Winnie the Pooh ", true }, + { "", true }, + { "", true }, + { "toto", false } + } }, + { make_shared(), { + { "", true }, + { "toto", false }, + { "", true }, + { "", true } + } }, + { make_shared(), { + { "", true }, + { "", true }, + { "", true }, + { "toto", false } + } }, + { make_shared(), { + { "abcd", false }, + { "1985-04-12T23:20:50.52Z", true }, + { "1996-12-19T16:39:57-08:00", true }, + { "1990-12-31T23:59:60Z", true }, + { "1990-12-31T15:59:60-08:00", true }, + { "2001-02-29T10:10:10Z", false }, + { "2000-02-29T10:10:10Z", true }, + { "1937-01-01T12:00:27.87+00:20", true }, + { "1937-01-01T12:00:27.87Z", true }, + { "1956", false } + } }, + { make_shared(), { + { "Eeyore's feeling very depressed today", true }, + { "🤣", true }, + { "hello", true } + } }, + { make_shared(), { + { "MyAlias ", true }, + { "What is this? - Barry Burton", false }, + { "", true }, + { "(), { + { "MyAlias.VitalHeader", true }, + { "MyAlias.VitalHeader,Test", true }, + { "MyAlias.VitalHeader,🤣", false } + } } + }; + + for (const auto &entry : entries) { + const shared_ptr header = entry.first; + string previousValue; + + for (const auto &test : entry.second) { + const bool result = header->setValue(test.first); + BC_ASSERT_EQUAL(result, test.second, bool, "%d"); + + const string value = header->getValue(); + + if (result) + BC_ASSERT_STRING_EQUAL(value.c_str(), test.first.c_str()); + else + BC_ASSERT_STRING_EQUAL(value.c_str(), previousValue.c_str()); + + previousValue = value; + } + } +} + +static void check_subject_header_language (void) { + Cpim::SubjectHeader subjectHeader; + + // Check for not defined language. + { + const string language = subjectHeader.getLanguage(); + BC_ASSERT_STRING_EQUAL(language.c_str(), ""); + } + + // Set valid language. + { + const string languageToSet = "fr"; + + BC_ASSERT_TRUE(subjectHeader.setLanguage(languageToSet)); + BC_ASSERT_TRUE(languageToSet == subjectHeader.getLanguage()); + + const string str = subjectHeader.asString(); + const string expected = "Subject:;lang=" + languageToSet + " \r\n"; + BC_ASSERT_STRING_EQUAL(str.c_str(), expected.c_str()); + } + + // Set invalid language. + { + const string languageToSet = "fr--"; + BC_ASSERT_FALSE(subjectHeader.setLanguage(languageToSet)); + BC_ASSERT_FALSE(languageToSet == subjectHeader.getLanguage()); + BC_ASSERT_FALSE(subjectHeader.isValid()); + } +} + +static void parse_rfc_example (void) { + const string str = "Content-type: Message/CPIM\r\n" + "\r\n" + "From: MR SANDERS \r\n" + "To: Depressed Donkey \r\n" + "DateTime: 2000-12-13T13:40:00-08:00\r\n" + "Subject: the weather will be fine today\r\n" + "Subject:;lang=fr beau temps prevu pour aujourd'hui\r\n" + "NS: MyFeatures \r\n" + "Require: MyFeatures.VitalMessageOption\r\n" + "MyFeatures.VitalMessageOption: Confirmation-requested\r\n" + "MyFeatures.WackyMessageOption: Use-silly-font\r\n" + "\r\n" + "Content-type text/xml; charset=utf-8\r\n" + "Content-ID: <1234567890@foo.com>\r\n" + "\r\n" + "" + "Here is the text of my message." + ""; + + shared_ptr message = Cpim::Message::createFromString(str); + if (!BC_ASSERT_PTR_NOT_NULL(message)) return; + + const string str2 = message->asString(); + BC_ASSERT_STRING_EQUAL(str2.c_str(), str.c_str()); +} + +static void parse_message_with_generic_header_parameters (void) { + const string str = "Content-type: Message/CPIM\r\n" + "\r\n" + "From: MR SANDERS \r\n" + "Test:;aaa=bbb;yes=no CheckMe\r\n" + "yaya: coucou\r\n" + "yepee:;good=bad ugly\r\n" + "\r\n" + "Content-type text/xml; charset=utf-8\r\n" + "Content-ID: <1234567890@foo.com>\r\n" + "\r\n" + "" + "Here is the text of my message." + ""; + + shared_ptr message = Cpim::Message::createFromString(str); + if (!BC_ASSERT_PTR_NOT_NULL(message)) return; + + const string str2 = message->asString(); + BC_ASSERT_STRING_EQUAL(str2.c_str(), str.c_str()); +} + +static void build_message (void) { + Cpim::Message message; + if (!BC_ASSERT_FALSE(message.isValid())) + return; + + // Set CPIM headers. + Cpim::GenericHeader contentTypeHeader; + if (!BC_ASSERT_TRUE(contentTypeHeader.setName("Content-Type"))) return; + if (!BC_ASSERT_TRUE(contentTypeHeader.setValue("Message/CPIM"))) return; + + if (!BC_ASSERT_TRUE(message.addCpimHeader(contentTypeHeader))) return; + + // Set message headers. + Cpim::FromHeader fromHeader; + if (!BC_ASSERT_TRUE(fromHeader.setValue("MR SANDERS "))) return; + + Cpim::ToHeader toHeader; + if (!BC_ASSERT_TRUE(toHeader.setValue("Depressed Donkey "))) return; + + Cpim::DateTimeHeader dateTimeHeader; + if (!BC_ASSERT_TRUE(dateTimeHeader.setValue("2000-12-13T13:40:00-08:00"))) return; + + Cpim::SubjectHeader subjectHeader; + if (!BC_ASSERT_TRUE(subjectHeader.setValue("the weather will be fine today"))) return; + + Cpim::SubjectHeader subjectWithLanguageHeader; + if (!BC_ASSERT_TRUE(subjectWithLanguageHeader.setValue("beau temps prevu pour aujourd'hui"))) return; + if (!BC_ASSERT_TRUE(subjectWithLanguageHeader.setLanguage("fr"))) return; + + Cpim::NsHeader nsHeader; + if (!BC_ASSERT_TRUE(nsHeader.setValue("MyFeatures "))) return; + + Cpim::RequireHeader requireHeader; + if (!BC_ASSERT_TRUE(requireHeader.setValue("MyFeatures.VitalMessageOption"))) return; + + Cpim::GenericHeader vitalMessageHeader; + if (!BC_ASSERT_TRUE(vitalMessageHeader.setName("MyFeatures.VitalMessageOption"))) return; + if (!BC_ASSERT_TRUE(vitalMessageHeader.setValue("Confirmation-requested"))) return; + + Cpim::GenericHeader wackyMessageHeader; + if (!BC_ASSERT_TRUE(wackyMessageHeader.setName("MyFeatures.WackyMessageOption"))) return; + if (!BC_ASSERT_TRUE(wackyMessageHeader.setValue("Use-silly-font"))) return; + + if (!BC_ASSERT_TRUE(message.addMessageHeader(fromHeader))) return; + if (!BC_ASSERT_TRUE(message.addMessageHeader(toHeader))) return; + if (!BC_ASSERT_TRUE(message.addMessageHeader(dateTimeHeader))) return; + if (!BC_ASSERT_TRUE(message.addMessageHeader(subjectHeader))) return; + if (!BC_ASSERT_TRUE(message.addMessageHeader(subjectWithLanguageHeader))) return; + if (!BC_ASSERT_TRUE(message.addMessageHeader(nsHeader))) return; + if (!BC_ASSERT_TRUE(message.addMessageHeader(requireHeader))) return; + if (!BC_ASSERT_TRUE(message.addMessageHeader(vitalMessageHeader))) return; + if (!BC_ASSERT_TRUE(message.addMessageHeader(wackyMessageHeader))) return; + + const string content = "Content-type text/xml; charset=utf-8\r\n" + "Content-ID: <1234567890@foo.com>\r\n" + "\r\n" + "" + "Here is the text of my message." + ""; + + if (!BC_ASSERT_TRUE(message.setContent(content))) return; + if (!BC_ASSERT_TRUE(message.isValid())) return; + + const string strMessage = message.asString(); + const string expectedMessage = "Content-Type: Message/CPIM\r\n" + "\r\n" + "From: MR SANDERS \r\n" + "To: Depressed Donkey \r\n" + "DateTime: 2000-12-13T13:40:00-08:00\r\n" + "Subject: the weather will be fine today\r\n" + "Subject:;lang=fr beau temps prevu pour aujourd'hui\r\n" + "NS: MyFeatures \r\n" + "Require: MyFeatures.VitalMessageOption\r\n" + "MyFeatures.VitalMessageOption: Confirmation-requested\r\n" + "MyFeatures.WackyMessageOption: Use-silly-font\r\n" + "\r\n" + "Content-type text/xml; charset=utf-8\r\n" + "Content-ID: <1234567890@foo.com>\r\n" + "\r\n" + "" + "Here is the text of my message." + ""; + + BC_ASSERT_STRING_EQUAL(strMessage.c_str(), expectedMessage.c_str()); +} + +test_t cpim_tests[] = { + TEST_NO_TAG("Parse minimal CPIM message", parse_minimal_message), + TEST_NO_TAG("Set generic header name", set_generic_header_name), + TEST_NO_TAG("Set generic header value", set_generic_header_value), + TEST_NO_TAG("Check core header names", check_core_header_names), + TEST_NO_TAG("Set core header values", set_core_header_values), + TEST_NO_TAG("Check Subject header language", check_subject_header_language), + TEST_NO_TAG("Parse RFC example", parse_rfc_example), + TEST_NO_TAG("Parse Message with generic header parameters", parse_message_with_generic_header_parameters), + TEST_NO_TAG("Build Message", build_message) +}; + +test_suite_t cpim_test_suite = { + "Cpim", NULL, NULL, liblinphone_tester_before_each, liblinphone_tester_after_each, + sizeof(cpim_tests) / sizeof(cpim_tests[0]), cpim_tests +}; diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index 15f55b0d6..dc5f07f9c 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -39,36 +39,40 @@ extern "C" { #endif -extern test_suite_t setup_test_suite; -extern test_suite_t register_test_suite; +extern test_suite_t account_creator_test_suite; extern test_suite_t call_test_suite; extern test_suite_t call_video_test_suite; -extern test_suite_t message_test_suite; -extern test_suite_t presence_test_suite; -extern test_suite_t presence_server_test_suite; -extern test_suite_t upnp_test_suite; +extern test_suite_t cpim_test_suite; +extern test_suite_t dtmf_test_suite; extern test_suite_t event_test_suite; extern test_suite_t flexisip_test_suite; -extern test_suite_t stun_test_suite; -extern test_suite_t remote_provisioning_test_suite; -extern test_suite_t quality_reporting_test_suite; extern test_suite_t log_collection_test_suite; -extern test_suite_t tunnel_test_suite; -extern test_suite_t player_test_suite; -extern test_suite_t dtmf_test_suite; -extern test_suite_t offeranswer_test_suite; -extern test_suite_t video_test_suite; -extern test_suite_t multicast_call_test_suite; +extern test_suite_t message_test_suite; extern test_suite_t multi_call_test_suite; +extern test_suite_t multicast_call_test_suite; +extern test_suite_t offeranswer_test_suite; +extern test_suite_t player_test_suite; +extern test_suite_t presence_server_test_suite; +extern test_suite_t presence_test_suite; extern test_suite_t proxy_config_test_suite; -extern test_suite_t account_creator_test_suite; +extern test_suite_t quality_reporting_test_suite; +extern test_suite_t register_test_suite; +extern test_suite_t remote_provisioning_test_suite; +extern test_suite_t setup_test_suite; +extern test_suite_t stun_test_suite; +extern test_suite_t tunnel_test_suite; +extern test_suite_t upnp_test_suite; +extern test_suite_t video_test_suite; + #ifdef VCARD_ENABLED extern test_suite_t vcard_test_suite; #endif + extern test_suite_t audio_bypass_suite; #if HAVE_SIPP extern test_suite_t complex_sip_call_test_suite; #endif + extern int manager_count; extern int liblinphone_tester_ipv6_available(void); diff --git a/tester/tester.c b/tester/tester.c index 40f8b43b9..51062890f 100644 --- a/tester/tester.c +++ b/tester/tester.c @@ -572,6 +572,7 @@ void liblinphone_tester_add_suites() { bc_tester_add_suite(&log_collection_test_suite); bc_tester_add_suite(&player_test_suite); bc_tester_add_suite(&dtmf_test_suite); + bc_tester_add_suite(&cpim_test_suite); #if defined(VIDEO_ENABLED) && defined(HAVE_GTK) bc_tester_add_suite(&video_test_suite); #endif From db52ddaa09e2fa395bf21dc08072a7558b00eabc Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 3 Aug 2017 14:04:22 +0200 Subject: [PATCH 12/16] Get same instance id across all message tests to prevent receiving messages from a previous test because of forking. --- tester/accountmanager.c | 17 +++++++++++++++-- tester/liblinphone_tester.h | 1 + tester/message_tester.c | 16 +++++++++++++--- tester/tester.c | 5 +++-- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/tester/accountmanager.c b/tester/accountmanager.c index 58e98a3cf..385717ed2 100644 --- a/tester/accountmanager.c +++ b/tester/accountmanager.c @@ -27,6 +27,7 @@ struct _Account{ int done; int created; char *phone_alias; + char *uuid; }; typedef struct _Account Account; @@ -49,6 +50,7 @@ static Account *account_new(LinphoneAddress *identity, const char *unique_id){ }; void account_destroy(Account *obj){ + if (obj->uuid) bctbx_free(obj->uuid); linphone_address_unref(obj->identity); linphone_address_unref(obj->modified_identity); ms_free(obj->password); @@ -198,7 +200,7 @@ void account_create_on_server(Account *account, const LinphoneProxyConfig *refcf ms_free(chatdb); } -static LinphoneAddress *account_manager_check_account(AccountManager *m, LinphoneProxyConfig *cfg,const char* phone_alias){ +static LinphoneAddress *account_manager_check_account(AccountManager *m, LinphoneProxyConfig *cfg, LinphoneCoreManager *cm){ LinphoneCore *lc=linphone_proxy_config_get_core(cfg); const char *identity=linphone_proxy_config_get_identity(cfg); LinphoneAddress *id_addr=linphone_address_new(identity); @@ -209,6 +211,7 @@ static LinphoneAddress *account_manager_check_account(AccountManager *m, Linphon ,NULL , linphone_address_get_username(id_addr) , linphone_address_get_domain(id_addr)); + const char *phone_alias = cm->phone_alias; if (!account||(phone_alias&&(!account->phone_alias||strcmp(phone_alias,account->phone_alias)!=0))){ if (account) { @@ -229,6 +232,16 @@ static LinphoneAddress *account_manager_check_account(AccountManager *m, Linphon account_create_on_server(account,cfg,phone_alias); } + if (liblinphone_tester_keep_uuid) { + /* create and/or set uuid */ + if (account->uuid == NULL) { + char tmp[64]; + sal_create_uuid(cm->lc->sal, tmp, sizeof(tmp)); + account->uuid = bctbx_strdup(tmp); + } + sal_set_uuid(cm->lc->sal, account->uuid); + } + /*remove previous auth info to avoid mismatching*/ if (original_ai) linphone_core_remove_auth_info(lc,original_ai); @@ -251,7 +264,7 @@ void linphone_core_manager_check_accounts(LinphoneCoreManager *m){ if (!liblinphonetester_show_account_manager_logs) linphone_core_set_log_level_mask(ORTP_ERROR|ORTP_FATAL); for(it=linphone_core_get_proxy_config_list(m->lc);it!=NULL;it=it->next){ LinphoneProxyConfig *cfg=(LinphoneProxyConfig *)it->data; - account_manager_check_account(am,cfg,m->phone_alias); + account_manager_check_account(am,cfg,m); } if (!liblinphonetester_show_account_manager_logs) linphone_core_set_log_level_mask(logmask); } diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index dc5f07f9c..3e77b518d 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -115,6 +115,7 @@ extern const char* test_username; extern const char* test_password; extern const char* test_route; extern const char* userhostsfile; +extern bool_t liblinphone_tester_keep_uuid; extern bool_t liblinphone_tester_tls_support_disabled; extern const MSAudioDiffParams audio_cmp_params; extern const char *liblinphone_tester_mire_id; diff --git a/tester/message_tester.c b/tester/message_tester.c index d3293b1c7..3f4616c38 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -2428,7 +2428,7 @@ test_t message_tests[] = { TEST_NO_TAG("Transfer message with download io error", transfer_message_with_download_io_error), TEST_NO_TAG("Transfer message upload cancelled", transfer_message_upload_cancelled), TEST_NO_TAG("Transfer message download cancelled", transfer_message_download_cancelled), - TEST_ONE_TAG("Transfer message using external body url", file_transfer_using_external_body_url, "LeaksMemory"), + TEST_NO_TAG("Transfer message using external body url", file_transfer_using_external_body_url), TEST_NO_TAG("Transfer 2 messages simultaneously", file_transfer_2_messages_simultaneously), TEST_NO_TAG("Text message denied", text_message_denied), TEST_NO_TAG("Info message", info_message), @@ -2488,10 +2488,20 @@ test_t message_tests[] = { TEST_NO_TAG("IM Encryption Engine b64", im_encryption_engine_b64) }; +static int message_tester_before_suite(void) { + liblinphone_tester_keep_uuid = TRUE; + return 0; +} + +static int message_tester_after_suite(void) { + liblinphone_tester_keep_uuid = FALSE; + return 0; +} + test_suite_t message_test_suite = { "Message", - NULL, - NULL, + message_tester_before_suite, + message_tester_after_suite, liblinphone_tester_before_each, liblinphone_tester_after_each, sizeof(message_tests) / sizeof(message_tests[0]), message_tests diff --git a/tester/tester.c b/tester/tester.c index 51062890f..9e4f2c10e 100644 --- a/tester/tester.c +++ b/tester/tester.c @@ -44,8 +44,9 @@ static int liblinphone_tester_keep_accounts_flag = 0; -static int liblinphone_tester_keep_record_files = FALSE; -static int liblinphone_tester_leak_detector_disabled = FALSE; +static bool_t liblinphone_tester_keep_record_files = FALSE; +static bool_t liblinphone_tester_leak_detector_disabled = FALSE; +bool_t liblinphone_tester_keep_uuid = FALSE; bool_t liblinphone_tester_tls_support_disabled = FALSE; int manager_count = 0; int leaked_objects_count = 0; From 03130daadc3654595e26dfe8fa53466bf00ba29c Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 3 Aug 2017 15:25:36 +0200 Subject: [PATCH 13/16] Prevent crash in message tests. --- coreapi/chat.c | 17 +++++++++-------- coreapi/chat_file_transfer.c | 8 ++++++-- coreapi/private.h | 1 + tester/message_tester.c | 3 +-- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/coreapi/chat.c b/coreapi/chat.c index b7ecd439b..2d0a9dc18 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -117,13 +117,14 @@ void linphone_chat_message_cbs_set_file_transfer_progress_indication( BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatMessage); static void _linphone_chat_room_destroy(LinphoneChatRoom *cr) { - bctbx_list_free_with_data(cr->transient_messages, (void (*)(void *))linphone_chat_message_release); - if (cr->received_rtt_characters) { - cr->received_rtt_characters = bctbx_list_free_with_data(cr->received_rtt_characters, (void (*)(void *))ms_free); - } linphone_chat_room_delete_composing_idle_timer(cr); linphone_chat_room_delete_composing_refresh_timer(cr); linphone_chat_room_delete_remote_composing_refresh_timer(cr); + bctbx_list_free_with_data(cr->transient_messages, (bctbx_list_free_func)linphone_chat_message_release); + if (cr->weak_messages != NULL) bctbx_list_free(cr->weak_messages); + if (cr->received_rtt_characters) { + cr->received_rtt_characters = bctbx_list_free_with_data(cr->received_rtt_characters, (bctbx_list_free_func)ms_free); + } if (cr->lc != NULL) { if (bctbx_list_find(cr->lc->chatrooms, cr)) { ms_error("LinphoneChatRoom[%p] is destroyed while still being used by the LinphoneCore. This is abnormal." @@ -138,7 +139,6 @@ static void _linphone_chat_room_destroy(LinphoneChatRoom *cr) { if (cr->pending_message) linphone_chat_message_destroy(cr->pending_message); ms_free(cr->peer); - if (cr->weak_messages != NULL) bctbx_list_free(cr->weak_messages); } void linphone_chat_message_set_state(LinphoneChatMessage *msg, LinphoneChatMessageState state) { @@ -313,6 +313,7 @@ void linphone_chat_room_release(LinphoneChatRoom *cr) { linphone_chat_room_delete_composing_refresh_timer(cr); linphone_chat_room_delete_remote_composing_refresh_timer(cr); bctbx_list_for_each(cr->weak_messages, (bctbx_list_iterate_func)linphone_chat_message_deactivate); + bctbx_list_for_each(cr->transient_messages, (bctbx_list_iterate_func)linphone_chat_message_deactivate); cr->lc = NULL; linphone_chat_room_unref(cr); } @@ -1717,11 +1718,11 @@ void linphone_chat_message_unref(LinphoneChatMessage *msg) { } static void linphone_chat_message_deactivate(LinphoneChatMessage *msg){ + if (msg->file_transfer_information != NULL) { + _linphone_chat_message_cancel_file_transfer(msg, FALSE); + } /*mark the chat msg as orphan (it has no chat room anymore)*/ msg->chat_room = NULL; - if (msg->file_transfer_information != NULL) { - linphone_chat_message_cancel_file_transfer(msg); - } } static void linphone_chat_message_release(LinphoneChatMessage *msg) { diff --git a/coreapi/chat_file_transfer.c b/coreapi/chat_file_transfer.c index 3d5e69378..c1fd37984 100644 --- a/coreapi/chat_file_transfer.c +++ b/coreapi/chat_file_transfer.c @@ -613,7 +613,7 @@ void linphone_chat_message_start_file_download(LinphoneChatMessage *msg, linphone_chat_message_download_file(msg); } -void linphone_chat_message_cancel_file_transfer(LinphoneChatMessage *msg) { +void _linphone_chat_message_cancel_file_transfer(LinphoneChatMessage *msg, bool_t unref) { if (msg->http_request) { if (msg->state == LinphoneChatMessageStateInProgress) { linphone_chat_message_set_state(msg, LinphoneChatMessageStateNotDelivered); @@ -625,7 +625,7 @@ void linphone_chat_message_cancel_file_transfer(LinphoneChatMessage *msg) { , msg , msg->chat_room); belle_http_provider_cancel_request(msg->chat_room->lc->http_provider, msg->http_request); - if (msg->dir == LinphoneChatMessageOutgoing) { + if ((msg->dir == LinphoneChatMessageOutgoing) && unref) { // must release it linphone_chat_message_unref(msg); } @@ -639,6 +639,10 @@ void linphone_chat_message_cancel_file_transfer(LinphoneChatMessage *msg) { } } +void linphone_chat_message_cancel_file_transfer(LinphoneChatMessage *msg) { + _linphone_chat_message_cancel_file_transfer(msg, TRUE); +} + void linphone_chat_message_set_file_transfer_filepath(LinphoneChatMessage *msg, const char *filepath) { if (msg->file_transfer_filepath != NULL) { ms_free(msg->file_transfer_filepath); diff --git a/coreapi/private.h b/coreapi/private.h index 33d8b9710..a43cb2713 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -657,6 +657,7 @@ void linphone_chat_message_set_state(LinphoneChatMessage *msg, LinphoneChatMessa void linphone_chat_message_set_is_secured(LinphoneChatMessage *msg, bool_t secured); void linphone_chat_message_send_delivery_notification(LinphoneChatMessage *cm, LinphoneReason reason); void linphone_chat_message_send_display_notification(LinphoneChatMessage *cm); +void _linphone_chat_message_cancel_file_transfer(LinphoneChatMessage *msg, bool_t unref); int linphone_chat_room_upload_file(LinphoneChatMessage *msg); void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg); LinphoneChatMessageCbs *linphone_chat_message_cbs_new(void); diff --git a/tester/message_tester.c b/tester/message_tester.c index 3f4616c38..cece23af6 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -1793,9 +1793,8 @@ void file_transfer_io_error_base(char *server_url, bool_t destroy_room) { linphone_chat_room_send_chat_message(chatroom, msg); BC_ASSERT_TRUE(wait_for_until(marie->lc, NULL, &marie->stat.number_of_LinphoneMessageInProgress, 1, 1000)); if (destroy_room) { - //since message is orphan, we do not expect to be notified of state change linphone_core_delete_chat_room(marie->lc, chatroom); - BC_ASSERT_FALSE(wait_for_until(marie->lc, NULL, &marie->stat.number_of_LinphoneMessageNotDelivered, 1, 1000)); + BC_ASSERT_TRUE(wait_for_until(marie->lc, NULL, &marie->stat.number_of_LinphoneMessageNotDelivered, 1, 1000)); } else { BC_ASSERT_TRUE(wait_for_until(marie->lc, NULL, &marie->stat.number_of_LinphoneMessageNotDelivered, 1, 3000)); } From dc9e64a2344adefa32901b4dd6d935559fa200ea Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 3 Aug 2017 15:49:14 +0200 Subject: [PATCH 14/16] Fix memory leak in external body url message test. --- tester/liblinphone_tester.h | 1 + tester/message_tester.c | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index 3e77b518d..95c034e24 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -168,6 +168,7 @@ typedef struct _stats { int number_of_LinphoneMessageDelivered; int number_of_LinphoneMessageNotDelivered; int number_of_LinphoneMessageFileTransferDone; + int number_of_LinphoneMessageFileTransferError; int number_of_LinphoneMessageDeliveredToUser; int number_of_LinphoneMessageDisplayed; int number_of_LinphoneIsComposingActiveReceived; diff --git a/tester/message_tester.c b/tester/message_tester.c index cece23af6..c7a7156cb 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -190,6 +190,7 @@ void liblinphone_tester_chat_message_msg_state_changed(LinphoneChatMessage *msg, return; case LinphoneChatMessageStateFileTransferError: counters->number_of_LinphoneMessageNotDelivered++; + counters->number_of_LinphoneMessageFileTransferError++; return; case LinphoneChatMessageStateFileTransferDone: counters->number_of_LinphoneMessageFileTransferDone++; @@ -698,10 +699,13 @@ static void file_transfer_using_external_body_url(void) { BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_LinphoneMessageReceived, 1)); if (marie->stat.last_received_chat_message) { + cbs = linphone_chat_message_get_callbacks(marie->stat.last_received_chat_message); + linphone_chat_message_cbs_set_msg_state_changed(cbs, liblinphone_tester_chat_message_msg_state_changed); linphone_chat_message_download_file(marie->stat.last_received_chat_message); } BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_LinphoneMessageExtBodyReceived, 1)); - BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &pauline->stat.number_of_LinphoneMessageInProgress, 1)); + BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &pauline->stat.number_of_LinphoneMessageDelivered, 1)); + BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_LinphoneMessageFileTransferError, 1)); linphone_core_manager_destroy(pauline); linphone_core_manager_destroy(marie); } From e61c25be5e750279e2bbe74f8d06b156b0fce527 Mon Sep 17 00:00:00 2001 From: Ronan Abhamon Date: Fri, 4 Aug 2017 09:35:05 +0200 Subject: [PATCH 15/16] feat(utils): add stoi function --- src/cpim/parser/cpim-parser.cpp | 16 ++++++++-------- src/utils/utils.cpp | 10 ++++++++++ src/utils/utils.h | 2 ++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/cpim/parser/cpim-parser.cpp b/src/cpim/parser/cpim-parser.cpp index 1235b0896..8d2a88e97 100644 --- a/src/cpim/parser/cpim-parser.cpp +++ b/src/cpim/parser/cpim-parser.cpp @@ -355,22 +355,22 @@ bool Cpim::Parser::coreHeaderIsValid(const string &headerV return false; // Check date. - const int year = stoi(headerValue.substr(0, 4)); + const int year = Utils::stoi(headerValue.substr(0, 4)); const bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; - const int month = stoi(headerValue.substr(5, 2)); + const int month = Utils::stoi(headerValue.substr(5, 2)); if (month < 1 || month > 12) return false; - const int day = stoi(headerValue.substr(8, 2)); + const int day = Utils::stoi(headerValue.substr(8, 2)); if (day < 1 || (month == 2 && isLeapYear ? day > 29 : day > daysInMonth[month - 1])) return false; // Check time. if ( - stoi(headerValue.substr(11, 2)) > 24 || - stoi(headerValue.substr(14, 2)) > 59 || - stoi(headerValue.substr(17, 2)) > 60 + Utils::stoi(headerValue.substr(11, 2)) > 24 || + Utils::stoi(headerValue.substr(14, 2)) > 59 || + Utils::stoi(headerValue.substr(17, 2)) > 60 ) return false; @@ -378,8 +378,8 @@ bool Cpim::Parser::coreHeaderIsValid(const string &headerV if (headerValue.back() != 'Z') { size_t length = headerValue.length(); if ( - stoi(headerValue.substr(length - 5, 2)) > 24 || - stoi(headerValue.substr(length - 2, 2)) > 59 + Utils::stoi(headerValue.substr(length - 5, 2)) > 24 || + Utils::stoi(headerValue.substr(length - 2, 2)) > 59 ) return false; } diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 9e32a883a..029baaa34 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -47,3 +47,13 @@ vector Utils::split (const string &str, const string &delimiter) { return out; } + +int Utils::stoi (const string &str, size_t *idx, int base) { + char *p; + int v = strtol(str.c_str(), &p, base); + + if (idx) + *idx = p - str.c_str(); + + return v; +} diff --git a/src/utils/utils.h b/src/utils/utils.h index 1fc468add..d061b9e35 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -33,6 +33,8 @@ namespace LinphonePrivate { inline std::vector split (const std::string &str, char delimiter) { return split(str, std::string(1, delimiter)); } + + int stoi (const std::string &str, size_t *idx = 0, int base = 10); } } From 7f1414a600de1ae24cde492525e86234fb0ab482 Mon Sep 17 00:00:00 2001 From: Erwan Croze Date: Fri, 4 Aug 2017 09:46:08 +0200 Subject: [PATCH 16/16] Fix Android build --- src/utils/utils.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 029baaa34..3fa701d60 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +#include + #include "utils.h" using namespace std;