diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index 030557456e3c1f38e41427c399a439df85709c19..6979a608f1d133f68df5e0ffd69a2bfb18546166 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -948,6 +948,18 @@ static void on_expire(SalOp *op) { } else if (linphone_event_get_subscription_state(lev) == LinphoneSubscriptionActive) { linphone_event_set_state(lev, LinphoneSubscriptionExpiring); } + + void *user_data = linphone_event_get_user_data(lev); + const char *event_name = linphone_event_get_name(lev); + if (user_data && event_name && linphone_event_is_internal(lev) && strcmp(event_name, "presence") == 0) { + LinphoneCore *lc = (LinphoneCore *)op->getSal()->getUserPointer(); + LinphoneAddress *identity_address = (LinphoneAddress *)user_data; + LinphoneAccount *account = linphone_core_find_account_by_identity_address(lc, identity_address); + if (account) { + lInfo() << "Presence publish about to expire, manually refreshing it for account [" << account << "]"; + LinphonePrivate::Account::toCpp(account)->sendPublish(); + } + } } static void on_notify_response(SalOp *op) { diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index a80592af966906fa739a34ec050007c465b0052b..0f777c8fe7666983d4ee3f26bbd31572adcf5acd 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -5201,7 +5201,8 @@ int linphone_core_send_publish(LinphoneCore *lc, LinphonePresenceModel *presence for (elem = linphone_core_get_account_list(lc); elem != NULL; elem = bctbx_list_next(elem)) { LinphoneAccount *acc = (LinphoneAccount *)elem->data; if (linphone_account_params_get_publish_enabled(linphone_account_get_params(acc))) { - Account::toCpp(acc)->sendPublish(presence); + Account::toCpp(acc)->setPresenceModel(presence); + Account::toCpp(acc)->sendPublish(); } } return 0; @@ -9598,3 +9599,23 @@ void linphone_core_enable_empty_chatrooms_deletion(LinphoneCore *core, bool_t en linphone_config_set_bool(core->config, "misc", "empty_chat_room_deletion", enable); L_GET_CPP_PTR_FROM_C_OBJECT(core)->enableEmptyChatroomsDeletion(enable); } + +LinphoneAccount *linphone_core_find_account_by_identity_address(const LinphoneCore *core, + const LinphoneAddress *identity_address) { + LinphoneAccount *found = NULL; + if (identity_address == NULL) return found; + + bctbx_list_t *account_it; + for (account_it = (bctbx_list_t *)linphone_core_get_account_list(core); account_it != NULL; + account_it = account_it->next) { + LinphoneAccount *account = (LinphoneAccount *)(account_it->data); + const LinphoneAccountParams *params = linphone_account_get_params(account); + const LinphoneAddress *address = linphone_account_params_get_identity_address(params); + if (linphone_address_weak_equal(address, identity_address)) { + found = account; + break; + } + } + + return found; +} diff --git a/coreapi/presence.c b/coreapi/presence.c index 8360afb3322e4b097342102a5d26d53d4ba86423..f43bbafbfb6f2a5c678aa5282530a54f94f1e388 100644 --- a/coreapi/presence.c +++ b/coreapi/presence.c @@ -758,6 +758,12 @@ linphone_presence_service_new(const char *id, LinphonePresenceBasicStatus basic_ return service; } +void linphone_presence_service_set_timestamp(LinphonePresenceService *service, time_t timestamp) { + if (service) { + service->timestamp = timestamp; + } +} + char *linphone_presence_service_get_id(const LinphonePresenceService *service) { if (service == NULL) return NULL; return ms_strdup(service->id); diff --git a/coreapi/private_functions.h b/coreapi/private_functions.h index fb9e6468c6b77c3ac747ec2c47c4584c300074b1..357875b5950942144d3fe427c815378a787756ab 100644 --- a/coreapi/private_functions.h +++ b/coreapi/private_functions.h @@ -174,6 +174,8 @@ const LinphoneAuthInfo *_linphone_core_find_auth_info(LinphoneCore *lc, const char *domain, const char *algorithm, bool_t ignore_realm); +LinphoneAccount *linphone_core_find_account_by_identity_address(const LinphoneCore *core, + const LinphoneAddress *identity_address); void linphone_auth_info_fill_belle_sip_event(const LinphoneAuthInfo *auth_info, belle_sip_auth_event *event); void linphone_core_fill_belle_sip_auth_event(LinphoneCore *lc, belle_sip_auth_event *event, @@ -231,6 +233,7 @@ void linphone_friend_remove_incoming_subscription(LinphoneFriend *lf, LinphonePr const char *linphone_friend_phone_number_to_sip_uri(LinphoneFriend *lf, const char *phone_number); const char *linphone_friend_sip_uri_to_phone_number(LinphoneFriend *lf, const char *uri); void linphone_friend_clear_presence_models(LinphoneFriend *lf); +void linphone_presence_service_set_timestamp(LinphonePresenceService *service, time_t timestamp); LinphoneFriend *linphone_friend_list_find_friend_by_inc_subscribe(const LinphoneFriendList *list, LinphonePrivate::SalOp *op); LinphoneFriend *linphone_friend_list_find_friend_by_out_subscribe(const LinphoneFriendList *list, @@ -648,6 +651,7 @@ LinphoneEvent *_linphone_core_create_publish( LinphoneCore *lc, LinphoneAccount *account, const LinphoneAddress *resource, const char *event, int expires); void linphone_event_unpublish(LinphoneEvent *lev); void linphone_event_set_current_callbacks(LinphoneEvent *ev, LinphoneEventCbs *cbs); +void linphone_event_set_manual_refresher_mode(LinphoneEvent *lev, bool_t manual); /** * Useful for out of dialog notify * */ diff --git a/coreapi/proxy.c b/coreapi/proxy.c index f7e05030b3b20c0f4719800a35be0b91eea1fcbc..be508b46b85e877d2fe91b36a5174cc56569961e 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -524,7 +524,8 @@ void linphone_proxy_config_set_realm(LinphoneProxyConfig *cfg, const char *realm } int linphone_proxy_config_send_publish(LinphoneProxyConfig *proxy, LinphonePresenceModel *presence) { - return Account::toCpp(proxy->account)->sendPublish(presence); + Account::toCpp(proxy->account)->setPresenceModel(presence); + return Account::toCpp(proxy->account)->sendPublish(); } void _linphone_proxy_config_unpublish(LinphoneProxyConfig *obj) { diff --git a/src/account/account.cpp b/src/account/account.cpp index 84dc5832e789cfedbad777711ff7a2e51bbae513..9b5274184145851de82da937f593fbb71ad9ff21 100644 --- a/src/account/account.cpp +++ b/src/account/account.cpp @@ -68,6 +68,7 @@ Account::~Account() { if (mServiceRouteAddress) linphone_address_unref(mServiceRouteAddress); if (mContactAddress) linphone_address_unref(mContactAddress); if (mContactAddressWithoutParams) linphone_address_unref(mContactAddressWithoutParams); + if (mPresenceModel) linphone_presence_model_unref(mPresenceModel); releaseOps(); } @@ -974,7 +975,10 @@ void Account::update() { } } if (mSendPublish && (mState == LinphoneRegistrationOk || mState == LinphoneRegistrationCleared)) { - sendPublish(mCore->presence_model); + if (mPresenceModel == nullptr) { + setPresenceModel(mCore->presence_model); + } + sendPublish(); mSendPublish = false; } } @@ -1008,64 +1012,110 @@ shared_ptr<EventPublish> Account::createPublish(const char *event, int expires) Event::toCpp(_linphone_core_create_publish(mCore, this->toC(), NULL, event, expires))->getSharedFromThis()); } -int Account::sendPublish(LinphonePresenceModel *presence) { - int err = 0; - LinphoneAddress *presentity_address = NULL; - char *contact = NULL; +void Account::setPresenceModel(LinphonePresenceModel *presence) { + if (mPresenceModel) { + linphone_presence_model_unref(mPresenceModel); + mPresenceModel = nullptr; + } + if (presence) mPresenceModel = linphone_presence_model_ref(presence); +} +int Account::sendPublish() { + if (mPresenceModel == nullptr) { + lError() << "No presence model has been set for this account, can't send the PUBLISH"; + return -1; + } + + int err = 0; if (mState == LinphoneRegistrationOk || mState == LinphoneRegistrationCleared) { - LinphoneContent *content; - char *presence_body; + int publishExpires = mParams->getPublishExpires(); + + if (mPresencePublishEvent != nullptr) { + LinphonePublishState state = mPresencePublishEvent->getState(); + if (state != LinphonePublishOk && state != LinphonePublishProgress) { + lInfo() << "Presence publish state is [" << linphone_publish_state_to_string(state) + << "], destroying it and creating a new one instead"; + mPresencePublishEvent->unref(); + mPresencePublishEvent = nullptr; + } + } + if (mPresencePublishEvent == nullptr) { - mPresencePublishEvent = createPublish("presence", mParams->getPublishExpires()); + mPresencePublishEvent = createPublish("presence", publishExpires); } + mPresencePublishEvent->setInternal(true); + if (publishExpires != 1) { + // Force manual refresh mode so we can go through this method again + // when PUBLISH is about to expire, so we can update the presence model timestamp + mPresencePublishEvent->setManualRefresherMode(true); + } + mPresencePublishEvent->setUserData(mParams->getIdentityAddress()); + + LinphoneConfig *config = linphone_core_get_config(mCore); + if (linphone_config_get_bool(config, "sip", "update_presence_model_timestamp_before_publish_expires_refresh", + FALSE)) { + unsigned int nbServices = linphone_presence_model_get_nb_services(mPresenceModel); + if (nbServices > 0) { + LinphonePresenceService *latest_service = + linphone_presence_model_get_nth_service(mPresenceModel, nbServices - 1); + linphone_presence_service_set_timestamp(latest_service, ms_time(NULL)); + } + } - if (linphone_presence_model_get_presentity(presence) == NULL) { - lInfo() << "No presentity set for model [" << presence << "], using identity from account [" << this->toC() - << "]"; - linphone_presence_model_set_presentity(presence, mParams->getIdentityAddress()); + if (linphone_presence_model_get_presentity(mPresenceModel) == NULL) { + lInfo() << "No presentity set for model [" << mPresenceModel << "], using identity from account [" + << this->toC() << "]"; + linphone_presence_model_set_presentity(mPresenceModel, mParams->getIdentityAddress()); } - if (!linphone_address_equal(linphone_presence_model_get_presentity(presence), mParams->getIdentityAddress())) { - lInfo() << "Presentity for model [" << presence << "] differ account [" << this->toC() + LinphoneAddress *presentity_address = NULL; + char *contact = NULL; + if (!linphone_address_equal(linphone_presence_model_get_presentity(mPresenceModel), + mParams->getIdentityAddress())) { + lInfo() << "Presentity for model [" << mPresenceModel << "] differ account [" << this->toC() << "], using account"; presentity_address = - linphone_address_clone(linphone_presence_model_get_presentity(presence)); /*saved, just in case*/ - if (linphone_presence_model_get_contact(presence)) { - contact = bctbx_strdup(linphone_presence_model_get_contact(presence)); + linphone_address_clone(linphone_presence_model_get_presentity(mPresenceModel)); /*saved, just in case*/ + if (linphone_presence_model_get_contact(mPresenceModel)) { + contact = bctbx_strdup(linphone_presence_model_get_contact(mPresenceModel)); } - linphone_presence_model_set_presentity(presence, mParams->getIdentityAddress()); - linphone_presence_model_set_contact(presence, NULL); /*it will be automatically computed*/ + linphone_presence_model_set_presentity(mPresenceModel, mParams->getIdentityAddress()); + linphone_presence_model_set_contact(mPresenceModel, NULL); /*it will be automatically computed*/ } - if (!(presence_body = linphone_presence_model_to_xml(presence))) { - lError() << "Cannot publish presence model [" << presence << "] for account [" << this->toC() + + char *presence_body; + if (!(presence_body = linphone_presence_model_to_xml(mPresenceModel))) { + lError() << "Cannot publish presence model [" << mPresenceModel << "] for account [" << this->toC() << "] because of xml serialization error"; return -1; } - content = linphone_content_new(); - linphone_content_set_buffer(content, (const uint8_t *)presence_body, strlen(presence_body)); - linphone_content_set_type(content, "application"); - linphone_content_set_subtype(content, "pidf+xml"); if (!mSipEtag.empty()) { mPresencePublishEvent->addCustomHeader("SIP-If-Match", mSipEtag); mSipEtag = ""; } + + LinphoneContent *content = linphone_content_new(); + linphone_content_set_buffer(content, (const uint8_t *)presence_body, strlen(presence_body)); + linphone_content_set_type(content, "application"); + linphone_content_set_subtype(content, "pidf+xml"); + err = mPresencePublishEvent->send(content); linphone_content_unref(content); ms_free(presence_body); + if (presentity_address) { - linphone_presence_model_set_presentity(presence, presentity_address); + linphone_presence_model_set_presentity(mPresenceModel, presentity_address); linphone_address_unref(presentity_address); } if (contact) { - linphone_presence_model_set_contact(presence, contact); + linphone_presence_model_set_contact(mPresenceModel, contact); bctbx_free(contact); } - } else mSendPublish = true; /*otherwise do not send publish if registration is in progress, this will be done later*/ + return err; } diff --git a/src/account/account.h b/src/account/account.h index aea24718b47cd9130edc3f8b5405d3ce836a28a4..b70ac211487a81267c11a97fa5c5702694dc22b3 100644 --- a/src/account/account.h +++ b/src/account/account.h @@ -97,7 +97,8 @@ public: bool check(); bool isAvpfEnabled() const; int getUnreadChatMessageCount() const; - int sendPublish(LinphonePresenceModel *presence); + void setPresenceModel(LinphonePresenceModel *presence); + int sendPublish(); void apply(LinphoneCore *lc); void notifyPublishStateChanged(LinphonePublishState state); void pauseRegister(); @@ -173,6 +174,7 @@ private: SalCustomHeader *mSentHeaders = nullptr; std::shared_ptr<EventPublish> mPresencePublishEvent = nullptr; + LinphonePresenceModel *mPresenceModel = nullptr; std::shared_ptr<Account> mDependency = nullptr; @@ -196,4 +198,4 @@ private: LINPHONE_END_NAMESPACE -#endif // ifndef _L_ACCOUNT_H_ +#endif // ifndef _L_ACCOUNT_H_ \ No newline at end of file diff --git a/src/event/event.cpp b/src/event/event.cpp index 1ebc477e8d13a2d2bbe7fd8de1767632470e269f..7e675e279be5775495a913b6141616303f69dae8 100644 --- a/src/event/event.cpp +++ b/src/event/event.cpp @@ -174,4 +174,8 @@ void Event::setUnrefWhenTerminated(bool unrefWhenTerminated) { mUnrefWhenTerminated = unrefWhenTerminated; } +void Event::setManualRefresherMode(bool manual) { + mOp->setManualRefresherMode(manual); +} + LINPHONE_END_NAMESPACE \ No newline at end of file diff --git a/src/event/event.h b/src/event/event.h index cd5aea6097f599c73ed3f9f10afa51a3b76fd178..768f54895efdee5d45e3df71c24c047a576e48b5 100644 --- a/src/event/event.h +++ b/src/event/event.h @@ -76,6 +76,7 @@ public: const LinphoneAddress *getResource() const; LinphonePrivate::SalEventOp *getOp() const; + void setManualRefresherMode(bool manual); int getExpires() const; void setExpires(int expires); diff --git a/tester/presence_server_tester.c b/tester/presence_server_tester.c index f2e485fe5c7e797768d6255366ebcb0b8e84740e..a4a9d5b00636b7414d6f03ef97ee515f40987a0a 100644 --- a/tester/presence_server_tester.c +++ b/tester/presence_server_tester.c @@ -350,7 +350,8 @@ static void subscribe_with_late_publish(void) { BC_ASSERT_TRUE(wait_for_until(pauline->lc, marie->lc, &pauline->stat.number_of_LinphonePresenceActivityBusy, 3, 5000)); /*re- schedule marie to clean up things*/ - /*simulate a rapid presence change to make sure only first and last are transmited*/ + /*simulate a rapid presence change to make sure only first and last are transmited */ + /* this tests if SIP-If-Match header based mechanism works */ presence = linphone_presence_model_new_with_activity(LinphonePresenceActivityAway, NULL); linphone_core_set_presence_model(marie->lc, presence); linphone_presence_model_unref(presence); @@ -1573,6 +1574,110 @@ static void publish_with_expires(void) { simple_publish_with_expire(2); } +static void publish_with_expire_timestamp_refresh_base(bool_t refresh_timestamps, + bool_t each_friend_subscribes_to_the_other) { + LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager *pauline = + linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); + + LinphoneConfig *marie_config = linphone_core_get_config(marie->lc); + linphone_config_set_bool(marie_config, "sip", "update_presence_model_timestamp_before_publish_expires_refresh", + refresh_timestamps); + + LinphoneAccount *marie_account = linphone_core_get_default_account(marie->lc); + const LinphoneAccountParams *marie_account_params = linphone_account_get_params(marie_account); + LinphoneAccountParams *new_params = linphone_account_params_clone(marie_account_params); + linphone_account_params_set_publish_expires(new_params, 10); + linphone_account_set_params(marie_account, new_params); + linphone_account_params_unref(new_params); + + const char *rls_uri = "sip:rls@sip.example.org"; + + // This is currently necessary for the refresh timestamp case!!! + if (each_friend_subscribes_to_the_other) { + // Marie adds Pauline as Friend + LinphoneFriendList *marie_lfl = linphone_core_create_friend_list(marie->lc); + linphone_friend_list_set_rls_uri(marie_lfl, rls_uri); + linphone_core_add_friend_list(marie->lc, marie_lfl); + + char *pauline_identity = linphone_address_as_string(pauline->identity); + LinphoneFriend *marie_pauline_friend = linphone_core_create_friend_with_address(marie->lc, pauline_identity); + + linphone_friend_list_add_friend(marie_lfl, marie_pauline_friend); + linphone_friend_list_update_subscriptions(marie_lfl); + linphone_friend_list_unref(marie_lfl); + + linphone_friend_unref(marie_pauline_friend); + } + + // Pauline adds Marie as Friend + LinphoneFriendList *pauline_lfl = linphone_core_create_friend_list(pauline->lc); + linphone_friend_list_set_rls_uri(pauline_lfl, rls_uri); + linphone_core_add_friend_list(pauline->lc, pauline_lfl); + + char *marie_identity = linphone_address_as_string(marie->identity); + LinphoneFriend *pauline_marie_friend = linphone_core_create_friend_with_address(pauline->lc, marie_identity); + + ms_free(marie_identity); + BC_ASSERT_PTR_NOT_NULL(pauline_marie_friend); + BC_ASSERT_EQUAL(linphone_friend_get_consolidated_presence(pauline_marie_friend), + LinphoneConsolidatedPresenceOffline, int, "%d"); + + linphone_friend_list_add_friend(pauline_lfl, pauline_marie_friend); + linphone_friend_list_update_subscriptions(pauline_lfl); + linphone_friend_list_unref(pauline_lfl); + + LinphoneCoreCbs *callbacks = linphone_factory_create_core_cbs(linphone_factory_get()); + linphone_core_cbs_set_publish_state_changed(callbacks, linphone_publish_state_changed); + _linphone_core_add_callbacks(marie->lc, callbacks, TRUE); + linphone_core_cbs_unref(callbacks); + + linphone_core_set_consolidated_presence(marie->lc, LinphoneConsolidatedPresenceOnline); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphonePublishOk, 2)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneConsolidatedPresenceOnline, 2)); + + BC_ASSERT_EQUAL(linphone_friend_get_consolidated_presence(pauline_marie_friend), LinphoneConsolidatedPresenceOnline, + int, "%d"); + const LinphonePresenceModel *model = linphone_friend_get_presence_model(pauline_marie_friend); + time_t first_timestamp = linphone_presence_model_get_timestamp(model); + + // Wait for PUBLISH refresh + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphonePublishOk, 3)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneConsolidatedPresenceOnline, 3)); + model = linphone_friend_get_presence_model(pauline_marie_friend); + time_t next_timestamp = linphone_presence_model_get_timestamp(model); + if (refresh_timestamps) { + BC_ASSERT_FALSE(first_timestamp == next_timestamp); + } else { + BC_ASSERT_TRUE(first_timestamp == next_timestamp); + } + + // Wait for PUBLISH refresh + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphonePublishOk, 4)); + BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneConsolidatedPresenceOnline, 4)); + model = linphone_friend_get_presence_model(pauline_marie_friend); + next_timestamp = linphone_presence_model_get_timestamp(model); + if (refresh_timestamps) { + BC_ASSERT_FALSE(first_timestamp == next_timestamp); + } else { + BC_ASSERT_TRUE(first_timestamp == next_timestamp); + } + + linphone_friend_unref(pauline_marie_friend); + linphone_core_manager_stop(marie); + linphone_core_manager_stop(pauline); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +static void publish_without_expire_timestamp_refresh(void) { + publish_with_expire_timestamp_refresh_base(FALSE, TRUE); +} + +static void publish_with_expire_timestamp_refresh(void) { + publish_with_expire_timestamp_refresh_base(TRUE, TRUE); +} + static void publish_with_dual_identity(void) { LinphoneCoreManager *pauline = linphone_core_manager_new("multi_account_rc"); const bctbx_list_t *proxies; @@ -2429,6 +2534,10 @@ test_t presence_server_tests[] = { TEST_NO_TAG("Simple Publish", simple_publish), TEST_NO_TAG("Publish with 2 identities", publish_with_dual_identity), TEST_NO_TAG("Simple Publish with expires", publish_with_expires), + TEST_ONE_TAG( + "Publish presence refresher without updated timestamps", publish_without_expire_timestamp_refresh, "presence"), + TEST_ONE_TAG( + "Publish presence refresher with updated timestamps", publish_with_expire_timestamp_refresh, "presence"), TEST_ONE_TAG("Publish with network state changes", publish_with_network_state_changes, "presence"), TEST_NO_TAG("Simple", simple), TEST_NO_TAG("Fast activity change", fast_activity_change),