Extend presence by adding permanent activities.

This commit is contained in:
Ghislain MARY 2026-07-03 14:57:13 +02:00
parent 41d4102735
commit 51ca55ba76
19 changed files with 1029 additions and 252 deletions

View file

@ -11,6 +11,11 @@ that bundles liblinphone and its dependencies as git submodules.
Please refer to CHANGELOG.md files of submodules (mainly: *liblinphone*, *mediastreamer2*, *ortp*) for the actual
changes made to these components.
## Unreleased
### Added
- Support of permanent activities in the presence. This enables the ability for the presence server to store
activities permanently, even if the PUBLISH expires.
### Changes
- Upgrade of Opus source code to version 1.5.2 to activate OSCE (deep PLC and LACE/noLACE).

View file

@ -1021,7 +1021,7 @@ LINPHONE_PUBLIC void linphone_core_set_add_admin_information_to_contact(Linphone
LinphonePresenceModel *
linphone_core_create_presence_model_for_consolidated_presence(BCTBX_UNUSED(LinphoneCore *lc),
LinphoneConsolidatedPresence presence);
char *linphone_presence_model_to_xml(LinphonePresenceModel *model);
LINPHONE_PUBLIC char *linphone_presence_model_to_xml(LinphonePresenceModel *model);
LinphoneVideoDefinition *linphone_video_definition_new(unsigned int width, unsigned int height, const char *name);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2025 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -1040,6 +1040,27 @@ LINPHONE_PUBLIC bool_t linphone_account_params_dtls_srtp_verify_cert_enabled(con
*/
LINPHONE_PUBLIC void linphone_account_params_enable_dtls_srtp_verify_cert(LinphoneAccountParams *params, bool_t enable);
/**
* Returns whether the echoed presence subscription is enabled or not.
* The echoed presence is the presence as it is returned by the presence server. If a user has multiple devices, one of
* these devices may set some permanent activities. This echoed presence will reflect these permanent activities on the
* other devices that did not set the permanent activities.
* @param params The #LinphoneAccountParams object. @notnil
* @return TRUE if the echoed presence subscription is enabled.
**/
LINPHONE_PUBLIC bool_t
linphone_account_params_echoed_presence_subscription_enabled(const LinphoneAccountParams *params);
/**
* Enables or disables the echoed presence subscription on the given #LinphoneAccountParams.
* Enabling does NOT trigger an additional SUBSCRIBE or PUBLISH; it modifies the existing
* presence SUBSCRIBE (adding local account identity).
* @param params The #LinphoneAccountParams object. @notnil
* @param enable TRUE to enable, FALSE to disable.
*/
LINPHONE_PUBLIC void linphone_account_params_enable_echoed_presence_subscription(LinphoneAccountParams *params,
bool_t enable);
/**
* @}
*/

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2022 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -389,6 +389,16 @@ LINPHONE_PUBLIC LinphoneConsolidatedPresence linphone_account_get_consolidated_p
LINPHONE_PUBLIC void linphone_account_set_consolidated_presence(LinphoneAccount *account,
LinphoneConsolidatedPresence presence);
/**
* Gets the echoed presence model for the given account.
* The echoed presence model reflects the presence information received from the presence server,
* including permanent activities, when the echoed presence subscription is enabled.
* @param account The #LinphoneAccount object. @notnil
* @return The #LinphonePresenceModel for the echoed presence, or NULL if echoed presence subscription is not enabled.
* @maybenil
*/
LINPHONE_PUBLIC LinphonePresenceModel *linphone_account_get_echoed_presence_model(const LinphoneAccount *account);
/**
* Detect if the given input is a phone number or not.
* @param account The #LinphoneAccount object, unused yet but may contain useful data. Can be NULL. @maybenil

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2022 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -584,6 +584,72 @@ LINPHONE_PUBLIC LinphoneStatus linphone_presence_person_add_activities_note(Linp
*/
LINPHONE_PUBLIC LinphoneStatus linphone_presence_person_clear_activities_notes(LinphonePresencePerson *person);
/**
* Adds a permanent activity to a presence person.
* @param person The #LinphonePresencePerson object for which to add a permanent activity. @notnil
* @param activity The #LinphonePresenceActivity object to add to the person. @notnil
* @return 0 if successful, a value < 0 in case of error.
*/
LINPHONE_PUBLIC LinphoneStatus linphone_presence_person_add_permanent_activity(LinphonePresencePerson *person,
LinphonePresenceActivity *activity);
/**
* Gets the nth permanent activity of a presence person.
* @param person The #LinphonePresencePerson object to get the activity from. @notnil
* @param index The index of the permanent activity to get (the first activity having the index 0).
* @return A pointer to a #LinphonePresenceActivity object if successful, NULL otherwise. @maybenil
*/
LINPHONE_PUBLIC LinphonePresenceActivity *
linphone_presence_person_get_nth_permanent_activity(const LinphonePresencePerson *person, unsigned int index);
/**
* Gets the number of permanent activities included in the presence person.
* @param person The #LinphonePresencePerson object to get the number of permanent activities from. @notnil
* @return The number of permanent activities included in the #LinphonePresencePerson object.
*/
LINPHONE_PUBLIC unsigned int linphone_presence_person_get_nb_permanent_activities(const LinphonePresencePerson *person);
/**
* Clears all the permanent activities of a presence person.
* @param person The #LinphonePresencePerson object for which to clear the permanent activities. @notnil
* @return 0 if successful, a value < 0 in case of error.
*/
LINPHONE_PUBLIC LinphoneStatus linphone_presence_person_clear_permanent_activities(LinphonePresencePerson *person);
/**
* Adds a permanent activities note to a presence person.
* @param person The #LinphonePresencePerson object for which to add a permanent activities note. @notnil
* @param note The #LinphonePresenceNote object to add to the person. @notnil
* @return 0 if successful, a value < 0 in case of error.
*/
LINPHONE_PUBLIC LinphoneStatus linphone_presence_person_add_permanent_activities_note(LinphonePresencePerson *person,
LinphonePresenceNote *note);
/**
* Gets the nth permanent activities note of a presence person.
* @param person The #LinphonePresencePerson object to get the activities note from. @notnil
* @param index The index of the permanent activities note to get (the first note having the index 0).
* @return A pointer to a #LinphonePresenceNote object if successful, NULL otherwise. @maybenil
*/
LINPHONE_PUBLIC LinphonePresenceNote *
linphone_presence_person_get_nth_permanent_activities_note(const LinphonePresencePerson *person, unsigned int index);
/**
* Gets the number of permanent activities notes included in the presence person.
* @param person The #LinphonePresencePerson object to get the number of permanent activities notes from. @notnil
* @return The number of permanent activities notes included in the #LinphonePresencePerson object.
*/
LINPHONE_PUBLIC unsigned int
linphone_presence_person_get_nb_permanent_activities_notes(const LinphonePresencePerson *person);
/**
* Clears all the permanent activities notes of a presence person.
* @param person The #LinphonePresencePerson object for which to clear the permanent activities notes. @notnil
* @return 0 if successful, a value < 0 in case of error.
*/
LINPHONE_PUBLIC LinphoneStatus
linphone_presence_person_clear_permanent_activities_notes(LinphonePresencePerson *person);
/*****************************************************************************
* PRESENCE ACTIVITY FUNCTIONS TO GET ACCESS TO ALL FUNCTIONALITIES *
****************************************************************************/
@ -730,7 +796,7 @@ LINPHONE_PUBLIC int linphone_presence_model_get_capabilities(const LinphonePrese
* @return whether or not the #LinphonePresenceModel object has a given capability.
*/
LINPHONE_PUBLIC bool_t linphone_presence_model_has_capability(const LinphonePresenceModel *model,
const LinphoneFriendCapability capability);
LinphoneFriendCapability capability);
/**
* Returns whether or not the #LinphonePresenceModel object has a given capability with a certain version.
@ -740,7 +806,7 @@ LINPHONE_PUBLIC bool_t linphone_presence_model_has_capability(const LinphonePres
* @return whether or not the #LinphonePresenceModel object has a given capability with a certain version.
*/
LINPHONE_PUBLIC bool_t linphone_presence_model_has_capability_with_version(const LinphonePresenceModel *model,
const LinphoneFriendCapability capability,
LinphoneFriendCapability capability,
float version);
/**
@ -750,8 +816,9 @@ LINPHONE_PUBLIC bool_t linphone_presence_model_has_capability_with_version(const
* @param version The wanted version to test.
* @return whether or not the #LinphonePresenceModel object has a given capability with a certain version or more.
*/
LINPHONE_PUBLIC bool_t linphone_presence_model_has_capability_with_version_or_more(
const LinphonePresenceModel *model, const LinphoneFriendCapability capability, float version);
LINPHONE_PUBLIC bool_t linphone_presence_model_has_capability_with_version_or_more(const LinphonePresenceModel *model,
LinphoneFriendCapability capability,
float version);
/**
* Returns the version of the capability of a #LinphonePresenceModel.
@ -760,7 +827,7 @@ LINPHONE_PUBLIC bool_t linphone_presence_model_has_capability_with_version_or_mo
* @return the version of the capability of a #LinphonePresenceModel or -1.0 if the model has not the capability.
*/
LINPHONE_PUBLIC float linphone_presence_model_get_capability_version(const LinphonePresenceModel *model,
const LinphoneFriendCapability capability);
LinphoneFriendCapability capability);
/**
* Increase the reference count of the #LinphonePresenceService object.

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2025 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -20,8 +20,6 @@
#include "account-params.h"
#include <set>
#include "c-wrapper/internal/c-tools.h"
#include "core/core.h"
#include "linphone/types.h"
@ -220,6 +218,11 @@ AccountParams::AccountParams(LinphoneCore *lc, bool useDefaultValues) {
mInstantMessagingEncryptionMandatory =
useDefaultValues ? !!linphone_config_get_default_int(lc->config, "proxy", "im_encryption_mandatory", 0) : 0;
mEchoedPresenceSubscriptionEnabled =
useDefaultValues
? linphone_config_get_default_int(lc->config, "proxy", "echoed_presence_subscription", FALSE) == TRUE
: false;
string supportedTags = lc ? linphone_config_get_default_string(lc->config, "proxy", "supported", "empty") : "empty";
if (useDefaultValues && supportedTags != "empty") {
vector<string> splitTags = bctoolbox::Utils::split(supportedTags, ",");
@ -352,6 +355,10 @@ AccountParams::AccountParams(LinphoneCore *lc, int index) : AccountParams(lc, fa
setPictureUri(linphone_config_get_string(config, key, "picture_uri", mPictureUri.c_str()));
enableEchoedPresenceSubscription(linphone_config_get_bool(config, key, "echoed_presence_subscription",
echoedPresenceSubscriptionEnabled() ? TRUE : FALSE) ==
TRUE);
string mwiServerUri = linphone_config_get_string(config, key, "mwi_server_uri", "");
mMwiServerAddress = nullptr;
if (!mwiServerUri.empty()) {
@ -470,6 +477,8 @@ AccountParams::AccountParams(const AccountParams &other) : HybridObject(other),
mSupportedTagsList = other.mSupportedTagsList;
mUseSupportedTags = other.mUseSupportedTags;
mEchoedPresenceSubscriptionEnabled = other.mEchoedPresenceSubscriptionEnabled;
}
AccountParams::~AccountParams() {
@ -1046,6 +1055,14 @@ std::shared_ptr<const Address> AccountParams::getVoicemailAddress() const {
return mVoicemailAddress;
}
bool AccountParams::echoedPresenceSubscriptionEnabled() const {
return mEchoedPresenceSubscriptionEnabled;
}
void AccountParams::enableEchoedPresenceSubscription(bool enabled) {
mEchoedPresenceSubscriptionEnabled = enabled;
}
// -----------------------------------------------------------------------------
LinphoneStatus AccountParams::setServerAddress(const std::shared_ptr<const Address> &serverAddr) {
@ -1268,6 +1285,8 @@ void AccountParams::writeToConfigFile(LinphoneConfig *config, int index) {
if (mVoicemailAddress) {
linphone_config_set_string(config, key, "voicemail_uri", getVoicemailAddressCstr());
}
linphone_config_set_bool(config, key, "echoed_presence_subscription",
mEchoedPresenceSubscriptionEnabled ? TRUE : FALSE);
linphone_config_set_bool(config, key, "im_encryption_mandatory", mInstantMessagingEncryptionMandatory);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2025 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -25,7 +25,6 @@
#include "address/address.h"
#include "c-wrapper/list-holder.h"
#include "linphone/api/c-push-notification-config.h"
#include "linphone/api/c-types.h"
#include "linphone/types.h"
#include "utils/custom-params.h"
@ -104,6 +103,7 @@ public:
void setInstantMessagingEncryptionMandatory(bool mandatory);
void setSupportedTagsList(const std::list<std::string> &supportedTagsList);
void enableDtlsSrtpVerifyCert(bool flag);
void enableEchoedPresenceSubscription(bool enabled);
// Getters
int getExpires() const;
@ -165,6 +165,7 @@ public:
const bctbx_list_t *getSupportedTagsCList() const;
bool useSupportedTags() const;
bool dtlsSrtpVerifyCertEnabled() const;
bool echoedPresenceSubscriptionEnabled() const;
// Other
LinphoneStatus setServerAddress(const std::shared_ptr<const Address> &serverAddr);
@ -206,6 +207,7 @@ private:
bool mDtlsSrtpVerifyCertEnabled;
bool mAllowCpimMessagesInBasicChatRooms;
bool mInstantMessagingEncryptionMandatory;
bool mEchoedPresenceSubscriptionEnabled;
void *mUserData;

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2025 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -22,31 +22,21 @@
#include "account.h"
#include "c-wrapper/c-wrapper.h"
#include "c-wrapper/internal/c-tools.h"
#include "chat/ics/ics.h"
#include "conference/client-conference.h"
#include "content/content.h"
#include "core/core.h"
#include "linphone/api/c-account-params.h"
#include "linphone/api/c-account.h"
#include "linphone/api/c-address.h"
#include "linphone/utils/algorithm.h"
#include "push-notification/push-notification-config.h"
#ifdef HAVE_ADVANCED_IM
#ifdef HAVE_LIME_X3DH
#include "chat/encryption/lime-x3dh-encryption-engine.h"
#endif // HAVE_LIME_X3DH
#endif // HAVE_ADVANCED_IM
#include "c-wrapper/c-wrapper.h"
#include "c-wrapper/internal/c-tools.h"
#include "core/core-p.h"
#include "core/core.h"
#include "db/main-db-p.h"
#include "event/event-publish.h"
#include "friend/friend.h"
#include "linphone/core.h"
#include "linphone/utils/algorithm.h"
#include "presence/presence-model.h"
#include "presence/presence-service.h"
#include "private.h"
#include "utils/custom-params.h"
#include "push-notification/push-notification-config.h"
#include "utils/fsm-integrity-checker.h"
#include "utils/xml-utils.h"
#ifdef HAVE_XERCESC
@ -1447,6 +1437,14 @@ LinphoneConsolidatedPresence Account::getConsolidatedPresence() const {
: LinphoneConsolidatedPresenceOffline;
}
const std::shared_ptr<PresenceModel> &Account::getEchoedPresenceModel() const {
return mEchoedPresenceModel;
}
void Account::setEchoedPresenceModel(const std::shared_ptr<PresenceModel> &presenceModel) {
mEchoedPresenceModel = presenceModel;
}
int Account::sendPublish() {
if (!mPresenceModel) {
lError() << "No presence model has been set for " << *this << ", can't send the PUBLISH";
@ -1483,7 +1481,7 @@ int Account::sendPublish() {
LinphoneConfig *config = linphone_core_get_config(getCCore());
if (linphone_config_get_bool(config, "sip", "update_presence_model_timestamp_before_publish_expires_refresh",
FALSE)) {
FALSE) == TRUE) {
unsigned int nbServices = mPresenceModel->getNbServices();
if (nbServices > 0) {
std::shared_ptr<PresenceService> latestService = mPresenceModel->getNthService(nbServices - 1);
@ -1531,6 +1529,7 @@ int Account::sendPublish() {
content->setContentType(contentType);
err = mPresencePublishEvent->send(content);
mPresenceModel->hasBeenPublished();
if (presentityAddress) {
lInfo() << "Restoring previous presentity address " << *presentityAddress << " for model ["
@ -1538,8 +1537,9 @@ int Account::sendPublish() {
mPresenceModel->setPresentity(presentityAddress);
mPresenceModel->setContact(contact);
}
} else
} else {
setSendPublish(true); /*otherwise do not send publish if registration is in progress, this will be done later*/
}
return err;
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2025 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -101,6 +101,7 @@ public:
void setPresenceModel(const std::shared_ptr<PresenceModel> &presence, bool needToSendPublish = true);
void setConsolidatedPresence(LinphoneConsolidatedPresence presence);
void setRemoveAuthInfoOnRegistrationCleared(bool removeAuthOnCleared);
void setEchoedPresenceModel(const std::shared_ptr<PresenceModel> &presenceModel);
// Getters
int getAuthFailure() const;
@ -121,6 +122,7 @@ public:
LimeUserAccountStatus getLimeUserAccountStatus() const;
const std::shared_ptr<PresenceModel> &getPresenceModel() const;
LinphoneConsolidatedPresence getConsolidatedPresence() const;
const std::shared_ptr<PresenceModel> &getEchoedPresenceModel() const;
int getUnreadChatMessageCount() const;
const std::list<std::shared_ptr<AbstractChatRoom>> &getChatRooms() const;
@ -131,7 +133,7 @@ public:
std::list<std::shared_ptr<CallLog>> getCallLogs() const;
std::list<std::shared_ptr<CallLog>> getCallLogsForAddress(const std::shared_ptr<const Address> &) const;
std::list<std::shared_ptr<ConferenceInfo>>
getConferenceInfos(const std::list<LinphoneStreamType> capabilities = {}) const;
getConferenceInfos(std::list<LinphoneStreamType> capabilities = {}) const;
void addConferenceInfo(const std::shared_ptr<ConferenceInfo> &info);
void ccmpConferenceInformationResponseReceived();
void ccmpConferenceInformationRequestSent();
@ -173,7 +175,7 @@ public:
static LinphoneAccountAddressComparisonResult compareLinphoneAddresses(const std::shared_ptr<const Address> &a,
const std::shared_ptr<const Address> &b);
static void writeAllToConfigFile(const std::shared_ptr<Core> core);
static void writeAllToConfigFile(std::shared_ptr<Core> core);
static void writeToConfigFile(LpConfig *config, const std::shared_ptr<Account> &account, int index);
// To be removed when not using proxy config anymore
@ -252,6 +254,7 @@ private:
std::shared_ptr<EventPublish> mPresencePublishEvent;
std::shared_ptr<PresenceModel> mPresenceModel;
std::shared_ptr<PresenceModel> mEchoedPresenceModel;
std::shared_ptr<Account> mDependency;
std::weak_ptr<Account> mDependee;
@ -292,7 +295,7 @@ private:
class AccountLogContextualizer : public CoreLogContextualizer {
public:
AccountLogContextualizer(const LinphoneAccount *account)
: CoreLogContextualizer(account ? Account::toCpp(account) : nullptr) {
: CoreLogContextualizer((account != nullptr) ? Account::toCpp(account) : nullptr) {
}
};

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2025 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -537,3 +537,11 @@ bool_t linphone_account_params_dtls_srtp_verify_cert_enabled(const LinphoneAccou
void linphone_account_params_enable_dtls_srtp_verify_cert(LinphoneAccountParams *params, bool_t enable) {
AccountParams::toCpp(params)->enableDtlsSrtpVerifyCert(enable);
}
bool_t linphone_account_params_echoed_presence_subscription_enabled(const LinphoneAccountParams *params) {
return AccountParams::toCpp(params)->echoedPresenceSubscriptionEnabled() ? TRUE : FALSE;
}
void linphone_account_params_enable_echoed_presence_subscription(LinphoneAccountParams *params, bool_t enable) {
AccountParams::toCpp(params)->enableEchoedPresenceSubscription(enable == TRUE);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2022 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -25,7 +25,6 @@
#include "account/account-params.h"
#include "account/account.h"
#include "c-wrapper/c-wrapper.h"
#include "core/core-p.h"
#include "dial-plan/dial-plan.h"
#include "linphone/api/c-account-cbs.h"
#include "linphone/api/c-account-params.h"
@ -304,6 +303,14 @@ void linphone_account_set_consolidated_presence(LinphoneAccount *account, Linpho
Account::toCpp(account)->setConsolidatedPresence(presence);
}
LinphonePresenceModel *linphone_account_get_echoed_presence_model(const LinphoneAccount *account) {
const auto &presenceModel = Account::toCpp(account)->getEchoedPresenceModel();
if (presenceModel) {
return presenceModel->toC();
}
return nullptr;
}
void linphone_account_set_presence_model(LinphoneAccount *account, LinphonePresenceModel *presence_model) {
linphone_account_set_presence_model_with_publish_toggle(account, presence_model, TRUE);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -18,8 +18,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cmath>
#include <bctoolbox/defs.h>
#include <bctoolbox/map.h>
@ -28,7 +26,6 @@
#include "linphone/api/c-address.h"
#include "linphone/core.h"
#include "linphone/lpconfig.h"
#include "linphone/presence.h"
#include "linphone/types.h"
#include "presence/presence-activity.h"
#include "presence/presence-model.h"
@ -408,6 +405,70 @@ LinphoneStatus linphone_presence_person_clear_activities_notes(LinphonePresenceP
return 0;
}
unsigned int linphone_presence_person_get_nb_permanent_activities(const LinphonePresencePerson *person) {
if (person == nullptr) {
return 0;
}
return PresencePerson::toCpp(person)->getNbPermanentActivities();
}
LinphonePresenceActivity *linphone_presence_person_get_nth_permanent_activity(const LinphonePresencePerson *person,
unsigned int index) {
if (person == nullptr) {
return nullptr;
}
std::shared_ptr<PresenceActivity> activity = PresencePerson::toCpp(person)->getNthPermanentActivity(index);
return activity ? activity->toC() : nullptr;
}
LinphoneStatus linphone_presence_person_add_permanent_activity(LinphonePresencePerson *person,
LinphonePresenceActivity *activity) {
if (person == nullptr || activity == nullptr) {
return -1;
}
return PresencePerson::toCpp(person)->addPermanentActivity(PresenceActivity::getSharedFromThis(activity));
}
LinphoneStatus linphone_presence_person_clear_permanent_activities(LinphonePresencePerson *person) {
if (person == nullptr) {
return -1;
}
PresencePerson::toCpp(person)->clearPermanentActivities();
return 0;
}
unsigned int linphone_presence_person_get_nb_permanent_activities_notes(const LinphonePresencePerson *person) {
if (person == nullptr) {
return 0;
}
return PresencePerson::toCpp(person)->getNbPermanentActivitiesNotes();
}
LinphonePresenceNote *linphone_presence_person_get_nth_permanent_activities_note(const LinphonePresencePerson *person,
unsigned int index) {
if (person == nullptr) {
return nullptr;
}
std::shared_ptr<PresenceNote> note = PresencePerson::toCpp(person)->getNthPermanentActivitiesNote(index);
return note ? note->toC() : nullptr;
}
LinphoneStatus linphone_presence_person_add_permanent_activities_note(LinphonePresencePerson *person,
LinphonePresenceNote *note) {
if (person == nullptr || note == nullptr) {
return -1;
}
return PresencePerson::toCpp(person)->addPermanentActivitiesNote(PresenceNote::getSharedFromThis(note));
}
LinphoneStatus linphone_presence_person_clear_permanent_activities_notes(LinphonePresencePerson *person) {
if (person == nullptr) {
return -1;
}
PresencePerson::toCpp(person)->clearPermanentActivitiesNotes();
return 0;
}
/*****************************************************************************
* PRESENCE ACTIVITY FUNCTIONS TO GET ACCESS TO ALL FUNCTIONALITIES *
****************************************************************************/
@ -707,8 +768,10 @@ void linphone_notify_parse_presence(const char *content_type,
const char *content_subtype,
const char *body,
SalPresenceModel **result) {
PresenceModel::parsePresence(L_C_TO_STRING(content_type), L_C_TO_STRING(content_subtype), L_C_TO_STRING(body),
result);
if (result != nullptr) {
*result = PresenceModel::parsePresence(L_C_TO_STRING(content_type), L_C_TO_STRING(content_subtype),
L_C_TO_STRING(body));
}
}
void linphone_notify_recv(LinphoneCore *lc, SalOp *op, SalSubscribeStatus ss, SalPresenceModel *model) {

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -581,46 +581,79 @@ std::string FriendList::createResourceListXml() const {
std::string xmlContent;
if (mFriendsMapByUri.empty()) {
lWarning() << __FUNCTION__ << ": Empty list in subscription, ignored.";
return std::string();
return {};
}
xmlBufferPtr buf = xmlBufferCreate();
if (!buf) {
if (buf == nullptr) {
lError() << __FUNCTION__ << ": Error creating the XML buffer";
return std::string();
return {};
}
xmlTextWriterPtr writer = xmlNewTextWriterMemory(buf, 0);
if (!writer) {
if (writer == nullptr) {
lError() << __FUNCTION__ << ": Error creating the XML writer";
xmlBufferFree(buf);
return std::string();
return {};
}
xmlTextWriterSetIndent(writer, 1);
int err = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", nullptr);
if (err >= 0)
if (err >= 0) {
err = xmlTextWriterStartElementNS(writer, nullptr, (const xmlChar *)"resource-lists",
(const xmlChar *)"urn:ietf:params:xml:ns:resource-lists");
if (err >= 0)
}
if (err >= 0) {
err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"xsi", nullptr,
(const xmlChar *)"http://www.w3.org/2001/XMLSchema-instance");
}
if (err >= 0) {
err = xmlTextWriterStartElement(writer, (const xmlChar *)"list");
}
// Add local identify urls if echoed presence subscription is enabled to get the echoed model from the presence
// server. This is to be able to get a coherent view of the permanent activities across all the devices.
for (const auto &account : getCore()->getAccounts()) {
if (account->getAccountParams()->echoedPresenceSubscriptionEnabled()) {
const std::shared_ptr<Address> localIdentity = account->getAccountParams()->getIdentityAddress();
if (localIdentity && !localIdentity->asStringUriOnly().empty()) {
if (err >= 0) {
err = xmlTextWriterStartElement(writer, (const xmlChar *)"entry");
}
if (err >= 0) {
err = xmlTextWriterWriteAttribute(writer, (const xmlChar *)"uri",
(const xmlChar *)localIdentity->asStringUriOnly().c_str());
}
if (err >= 0) {
err = xmlTextWriterEndElement(writer); // Close the "entry" element.
}
}
}
}
std::string previousEntry;
for (const auto &entry : mFriendsMapByUri) {
// Map is sorted, prevent duplicates
if (previousEntry.empty() || (previousEntry != entry.first)) {
if (err >= 0) err = xmlTextWriterStartElement(writer, (const xmlChar *)"entry");
if (err >= 0)
if (err >= 0) {
err = xmlTextWriterStartElement(writer, (const xmlChar *)"entry");
}
if (err >= 0) {
err = xmlTextWriterWriteAttribute(writer, (const xmlChar *)"uri", (const xmlChar *)entry.first.c_str());
if (err >= 0) err = xmlTextWriterEndElement(writer); // Close the "entry" element.
}
if (err >= 0) {
err = xmlTextWriterEndElement(writer); // Close the "entry" element.
}
}
previousEntry = entry.first;
}
if (err >= 0) err = xmlTextWriterEndElement(writer); // Close the "list" element.
if (err >= 0) err = xmlTextWriterEndElement(writer); // Close the "resource-lists" element.
if (err >= 0) err = xmlTextWriterEndDocument(writer);
if (err >= 0) {
err = xmlTextWriterEndElement(writer); // Close the "list" element.
}
if (err >= 0) {
err = xmlTextWriterEndElement(writer); // Close the "resource-lists" element.
}
if (err >= 0) {
err = xmlTextWriterEndDocument(writer);
}
if (err > 0) {
// xmlTextWriterEndDocument returns the size of the content.
xmlContent = (char *)buf->content;
@ -634,7 +667,7 @@ std::string FriendList::createResourceListXml() const {
std::string FriendList::createResourceListXml() const {
lWarning() << "FriendList::createResourceListXml() is stubbed.";
return std::string();
return {};
}
#endif
@ -764,7 +797,7 @@ class FriendListXmlException : public std::exception {
public:
FriendListXmlException(const char *msg) : mMessage(msg) {
}
const char *what() const throw() override {
[[nodiscard]] const char *what() const noexcept override {
return mMessage;
}
@ -785,7 +818,9 @@ void FriendList::parseMultipartRelatedBody(const std::shared_ptr<const Content>
xmlXPathRegisterNs(xmlCtx.getXpathContext(), reinterpret_cast<const xmlChar *>("rlmi"),
reinterpret_cast<const xmlChar *>("urn:ietf:params:xml:ns:rlmi"));
std::string versionStr = xmlCtx.getAttributeTextContent("/rlmi:list", "version");
if (versionStr.empty()) throw FriendListXmlException("rlmi+xml: No version attribute in list");
if (versionStr.empty()) {
throw FriendListXmlException("rlmi+xml: No version attribute in list");
}
int version = atoi(versionStr.c_str());
if (version < mExpectedNotificationVersion) {
// No longer an error as dialog may be silently restarting by the refresher
@ -793,42 +828,54 @@ void FriendList::parseMultipartRelatedBody(const std::shared_ptr<const Content>
<< mExpectedNotificationVersion << ", dialog may have been reseted";
}
std::string fullStateStr = xmlCtx.getAttributeTextContent("/rlmi:list", "fullState");
if (fullStateStr.empty()) throw FriendListXmlException("rlmi+xml: No fullState attribute in list");
if (fullStateStr.empty()) {
throw FriendListXmlException("rlmi+xml: No fullState attribute in list");
}
bool fullState = false;
std::string fullStateString(fullStateStr);
if ((fullStateString == "true") || (fullStateString == "1")) {
fullState = true;
for (const auto &lf : mFriendsList.mList)
for (const auto &lf : mFriendsList.mList) {
lf->clearPresenceModels();
}
}
if ((mExpectedNotificationVersion == 0) && !fullState)
if ((mExpectedNotificationVersion == 0) && !fullState) {
throw FriendListXmlException("rlmi+xml: Notification with version 0 is not full state, this is not valid");
}
mExpectedNotificationVersion = version + 1;
xmlXPathObjectPtr nameObject = xmlCtx.getXpathObjectForNodeList("/rlmi:list/rlmi:resource/rlmi:name/..");
if (nameObject && nameObject->nodesetval) {
if (nameObject != nullptr && nameObject->nodesetval != nullptr) {
for (int i = 1; i <= nameObject->nodesetval->nodeNr; i++) {
xmlCtx.setXpathContextNode(xmlXPathNodeSetItem(nameObject->nodesetval, i - 1));
std::string name = xmlCtx.getTextContent("./rlmi:name");
std::string uri = xmlCtx.getTextContent("./@uri");
if (uri.empty()) continue;
if (uri.empty()) {
continue;
}
std::shared_ptr<Address> addr = Address::create(uri);
if (!addr) continue;
if (!addr) {
continue;
}
std::shared_ptr<Friend> lf = findFriendByAddress(addr);
if (!lf && mBodylessSubscription) {
lf = Friend::create(getCore(), uri);
addFriend(lf);
}
if (!name.empty()) lf->setName(name);
if (!name.empty()) {
lf->setName(name);
}
}
}
if (nameObject) xmlXPathFreeObject(nameObject);
if (nameObject != nullptr) {
xmlXPathFreeObject(nameObject);
}
std::set<std::shared_ptr<Friend>> listFriendsPresenceReceived;
bctbx_list_t *parts = linphone_content_get_parts(content->toC());
xmlXPathObjectPtr resourceObject =
xmlCtx.getXpathObjectForNodeList("/rlmi:list/rlmi:resource/rlmi:instance[@state=\"active\"]/..");
if (resourceObject && resourceObject->nodesetval) {
if (resourceObject != nullptr && resourceObject->nodesetval != nullptr) {
for (int i = 1; i <= resourceObject->nodesetval->nodeNr; i++) {
xmlCtx.setXpathContextNode(xmlXPathNodeSetItem(resourceObject->nodesetval, i - 1));
std::string cid = xmlCtx.getTextContent("./rlmi:instance/@cid");
@ -836,9 +883,9 @@ void FriendList::parseMultipartRelatedBody(const std::shared_ptr<const Content>
std::shared_ptr<Content> presencePart = nullptr;
bctbx_list_t *it = parts;
while (it != nullptr) {
LinphoneContent *content = (LinphoneContent *)it->data;
auto *content = (LinphoneContent *)it->data;
const char *header = linphone_content_get_custom_header(content, "Content-Id");
if (header && Utils::iequalsIgnoreBrakets(header, cid)) {
if (header != nullptr && Utils::iequalsIgnoreBrakets(header, cid)) {
presencePart = Content::toCpp(content)->getSharedFromThis();
break;
}
@ -847,21 +894,26 @@ void FriendList::parseMultipartRelatedBody(const std::shared_ptr<const Content>
if (!presencePart) {
lWarning() << "rlmi+xml: Cannot find part with Content-Id: " << cid;
} else {
SalPresenceModel *presence = nullptr;
const ContentType &presencePartContentType = presencePart->getContentType();
PresenceModel::parsePresence(presencePartContentType.getType(),
presencePartContentType.getSubType(),
presencePart->getBodyAsUtf8String(), &presence);
if (presence) {
SalPresenceModel *presence = PresenceModel::parsePresence(presencePartContentType.getType(),
presencePartContentType.getSubType(),
presencePart->getBodyAsUtf8String());
if (presence != nullptr) {
// Try to reduce CPU cost of linphone_address_new and find_friend_by_address by only doing
// it when we know for sure we have a presence to notify
std::string uri = xmlCtx.getTextContent("./@uri");
if (uri.empty()) continue;
if (uri.empty()) {
continue;
}
std::shared_ptr<Address> addr = Address::create(uri);
if (!addr) continue;
if (!addr) {
continue;
}
// Clean the URI
if (addr->hasUriParam("gr")) addr->removeUriParam("gr");
if (addr->hasUriParam("gr")) {
addr->removeUriParam("gr");
}
uri = addr->asStringUriOnly();
const auto [first, last] = mFriendsMapByUri.equal_range(uri);
@ -873,13 +925,27 @@ void FriendList::parseMultipartRelatedBody(const std::shared_ptr<const Content>
getSharedFromThis(), uri,
PresenceModel::toCpp((LinphonePresenceModel *)presence)->getSharedFromThis());
listFriendsPresenceReceived.insert(lf);
} else {
for (const auto &account : getCore()->getAccounts()) {
if (account->getAccountParams()->echoedPresenceSubscriptionEnabled()) {
const std::shared_ptr<Address> localIdentity =
account->getAccountParams()->getIdentityAddress();
if (localIdentity && localIdentity->asStringUriOnly() == uri) {
account->setEchoedPresenceModel(
PresenceModel::toCpp(
reinterpret_cast<LinphonePresenceModel *>(presence))
->getSharedFromThis());
}
}
}
}
} else {
// Save the equal_range iterators for looping because mFriendsMapByUri might
// change during the loop, leading to wrong presence notifications
std::list<std::multimap<std::string, std::shared_ptr<Friend>>::iterator> its;
for (auto it = first; it != last; it++)
for (auto it = first; it != last; it++) {
its.push_back(it);
}
for (const auto &it : its) {
it->second->presenceReceived(
getSharedFromThis(), uri,
@ -897,15 +963,18 @@ void FriendList::parseMultipartRelatedBody(const std::shared_ptr<const Content>
// Notify list with all friends for which we received presence information
if (!listFriendsPresenceReceived.empty()) {
bctbx_list_t *l = nullptr;
for (const auto &lf : listFriendsPresenceReceived)
for (const auto &lf : listFriendsPresenceReceived) {
l = bctbx_list_append(l, lf->toC());
}
LINPHONE_HYBRID_OBJECT_INVOKE_CBS(FriendList, this, linphone_friend_list_cbs_get_presence_received, l);
bctbx_list_free(l);
}
}
bctbx_list_free_with_data(parts, (void (*)(void *))linphone_content_unref);
if (resourceObject) xmlXPathFreeObject(resourceObject);
if (resourceObject != nullptr) {
xmlXPathFreeObject(resourceObject);
}
} catch (FriendListXmlException &e) {
lWarning() << e.what();
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -18,6 +18,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef _MSC_VER
// Prevent compilation error with std::max, because Visual Studio defines a max macro.
// This define prevents Visual Studio from defining with max macro.
#define NOMINMAX
#endif
#include <algorithm>
#include <numeric>
#include <bctoolbox/defs.h>
@ -127,7 +134,9 @@ std::shared_ptr<PresenceActivity> PresenceModel::getActivity() const {
/* Suppose that if at least one service is open, then the model is open. */
LinphonePresenceBasicStatus PresenceModel::getBasicStatus() const {
for (const auto &service : mServices) {
if (service->getBasicStatus() == LinphonePresenceBasicStatusOpen) return LinphonePresenceBasicStatusOpen;
if (service->getBasicStatus() == LinphonePresenceBasicStatusOpen) {
return LinphonePresenceBasicStatusOpen;
}
}
return LinphonePresenceBasicStatusClosed;
}
@ -137,7 +146,9 @@ int PresenceModel::getCapabilities() const {
for (const auto &service : mServices) {
for (const auto &pair : service->mCapabilities) {
LinphoneFriendCapability capability = Friend::nameToCapability(pair.first);
if (capability != LinphoneFriendCapabilityNone) capabilities |= capability;
if (capability != LinphoneFriendCapabilityNone) {
capabilities |= capability;
}
}
}
return capabilities;
@ -147,15 +158,21 @@ float PresenceModel::getCapabilityVersion(const LinphoneFriendCapability capabil
auto it = std::max_element(mServices.cbegin(), mServices.cend(), [&](const auto &a, const auto &b) {
return a->getCapabilityVersion(capability) < b->getCapabilityVersion(capability);
});
if (it == mServices.cend()) return -1;
if (it == mServices.cend()) {
return -1;
}
return (*it)->getCapabilityVersion(capability);
}
LinphoneConsolidatedPresence PresenceModel::getConsolidatedPresence() const {
if (isOnline()) return LinphoneConsolidatedPresenceOnline;
if (isOnline()) {
return LinphoneConsolidatedPresenceOnline;
}
if (getBasicStatus() == LinphonePresenceBasicStatusClosed) {
unsigned int nbActivities = getNbActivities();
if (nbActivities == 0) return LinphoneConsolidatedPresenceOffline;
if (nbActivities == 0) {
return LinphoneConsolidatedPresenceOffline;
}
return LinphoneConsolidatedPresenceDoNotDisturb;
}
return LinphoneConsolidatedPresenceBusy;
@ -164,7 +181,9 @@ LinphoneConsolidatedPresence PresenceModel::getConsolidatedPresence() const {
const std::string &PresenceModel::getContact() const {
for (const auto &service : mServices) {
const std::string &contact = service->getContact();
if (!contact.empty()) return contact;
if (!contact.empty()) {
return contact;
}
}
return emptyString;
}
@ -172,12 +191,14 @@ const std::string &PresenceModel::getContact() const {
time_t PresenceModel::getLatestActivityTimestamp() const {
auto it = std::max_element(mPersons.cbegin(), mPersons.cend(),
[](const auto &a, const auto &b) { return a->getTimestamp() < b->getTimestamp(); });
if (it == mPersons.cend()) return static_cast<time_t>(-1);
if (it == mPersons.cend()) {
return static_cast<time_t>(-1);
}
return (*it)->getTimestamp();
}
unsigned int PresenceModel::getNbActivities() const {
return std::accumulate(mPersons.cbegin(), mPersons.cend(), 0u,
return std::accumulate(mPersons.cbegin(), mPersons.cend(), 0U,
[](unsigned int a, const auto &person) { return a + person->getNbActivities(); });
}
@ -192,22 +213,32 @@ unsigned int PresenceModel::getNbServices() const {
std::shared_ptr<PresenceNote> PresenceModel::getNote(const std::string &lang) const {
/* First try to find a note in the specified language exactly. */
std::shared_ptr<PresenceNote> note = findNoteWithLang(lang);
if (note) return note;
if (note) {
return note;
}
/* No notes in the specified language has been found, try to find one without language. */
note = findNoteWithLang(std::string());
if (note) return note;
if (note) {
return note;
}
/* Still no result, so get the first note even if it is not in the specified language. */
for (const auto &person : mPersons) {
note = person->getNthActivitiesNote(0);
if (note) return note;
if (note) {
return note;
}
note = person->getNthNote(0);
if (note) return note;
if (note) {
return note;
}
}
for (const auto &service : mServices) {
note = service->getNthNote(0);
if (note) return note;
if (note) {
return note;
}
}
return getNthNote(0);
}
@ -252,12 +283,12 @@ const std::shared_ptr<Address> &PresenceModel::getPresentity() const {
}
time_t PresenceModel::getTimestamp() const {
time_t timestamp = static_cast<time_t>(-1);
auto timestamp = static_cast<time_t>(-1);
for (const auto &service : mServices) {
if (service->getTimestamp() > timestamp) timestamp = service->getTimestamp();
timestamp = std::max(service->getTimestamp(), timestamp);
}
for (const auto &person : mPersons) {
if (person->getTimestamp() > timestamp) timestamp = person->getTimestamp();
timestamp = std::max(person->getTimestamp(), timestamp);
}
return timestamp;
}
@ -265,7 +296,9 @@ time_t PresenceModel::getTimestamp() const {
// -----------------------------------------------------------------------------
LinphoneStatus PresenceModel::addActivity(const std::shared_ptr<PresenceActivity> &activity) {
if (!activity) return -1;
if (!activity) {
return -1;
}
std::shared_ptr<PresencePerson> person;
if (getNbPersons() == 0) {
/* There is no person in the presence model, add one. */
@ -280,7 +313,9 @@ LinphoneStatus PresenceModel::addActivity(const std::shared_ptr<PresenceActivity
}
LinphoneStatus PresenceModel::addNote(const std::string &content, const std::string &lang) {
if (content.empty()) return -1;
if (content.empty()) {
return -1;
}
/* Will put the note in the first service. */
std::shared_ptr<PresenceService> service = getNthService(0);
if (!service) {
@ -302,7 +337,9 @@ LinphoneStatus PresenceModel::addNote(const std::string &content, const std::str
}
LinphoneStatus PresenceModel::addPerson(const std::shared_ptr<PresencePerson> &person) {
if (!person) return -1;
if (!person) {
return -1;
}
auto it = std::lower_bound(mPersons.cbegin(), mPersons.cend(), person->getTimestamp(),
[](const auto &person, time_t timestamp) { return person->getTimestamp() < timestamp; });
mPersons.insert(it, person);
@ -310,21 +347,26 @@ LinphoneStatus PresenceModel::addPerson(const std::shared_ptr<PresencePerson> &p
}
LinphoneStatus PresenceModel::addService(const std::shared_ptr<PresenceService> &service) {
if (!service) return -1;
if (!service) {
return -1;
}
mServices.push_back(service);
return 0;
}
void PresenceModel::clearActivities() {
for (auto &person : mPersons)
for (auto &person : mPersons) {
person->clearActivities();
}
}
void PresenceModel::clearNotes() {
for (auto &person : mPersons)
for (auto &person : mPersons) {
person->clearNotes();
for (auto &service : mServices)
}
for (auto &service : mServices) {
service->clearNotes();
}
mNotes.clear();
}
@ -337,68 +379,89 @@ void PresenceModel::clearServices() {
}
bool PresenceModel::hasCapability(const LinphoneFriendCapability capability) const {
return getCapabilities() & capability;
return (getCapabilities() & capability) != 0;
}
bool PresenceModel::hasCapabilityWithVersion(const LinphoneFriendCapability capability, float version) const {
for (const auto &service : mServices) {
if (service->hasCapabilityWithVersion(capability, version)) return true;
}
return false;
return std::any_of(mServices.begin(), mServices.end(), [capability, version](const auto &service) {
return service->hasCapabilityWithVersion(capability, version);
});
}
bool PresenceModel::hasCapabilityWithVersionOrMore(const LinphoneFriendCapability capability, float version) const {
for (const auto &service : mServices) {
if (service->hasCapabilityWithVersionOrMore(capability, version)) return true;
}
return false;
return std::any_of(mServices.begin(), mServices.end(), [capability, version](const auto &service) {
return service->hasCapabilityWithVersionOrMore(capability, version);
});
}
bool PresenceModel::isOnline() const {
return (mIsOnline || ((getBasicStatus() == LinphonePresenceBasicStatusOpen) && (getNbActivities() == 0)));
}
void PresenceModel::hasBeenPublished() {
for (const auto &person : mPersons) {
person->mPermanentActivitiesToBePublished = false;
}
}
#ifdef HAVE_XML2
int PresenceModel::parsePidfXmlPresenceNotes(XmlParsingContext &xmlContext) {
std::stringstream ss;
xmlXPathObjectPtr noteObject = xmlContext.getXpathObjectForNodeList("/pidf:presence/pidf:note");
if (noteObject && noteObject->nodesetval) {
if ((noteObject != nullptr) && (noteObject->nodesetval != nullptr)) {
for (int i = 1; i <= noteObject->nodesetval->nodeNr; i++) {
ss.clear(), ss.str(std::string()), ss << "/pidf:presence/pidf:note[" << i << "]";
std::string noteStr = xmlContext.getTextContent(ss.str());
if (noteStr.empty()) continue;
if (noteStr.empty()) {
continue;
}
ss.clear(), ss.str(std::string()), ss << "/pidf:presence/pidf:note[" << i << "]/@xml:lang";
std::string lang = xmlContext.getTextContent(ss.str());
std::shared_ptr<PresenceNote> note = PresenceNote::create(noteStr, lang);
mNotes.push_back(note);
}
}
if (noteObject) xmlXPathFreeObject(noteObject);
if (noteObject != nullptr) {
xmlXPathFreeObject(noteObject);
}
return 0;
}
int PresenceModel::parsePidfXmlPresencePersons(XmlParsingContext &xmlContext) {
stringstream ss;
time_t timestamp = static_cast<time_t>(-1);
auto timestamp = static_cast<time_t>(-1);
int err = 0;
xmlXPathObjectPtr personObject = xmlContext.getXpathObjectForNodeList(PresencePerson::pidfXmlPrefix.data());
if (personObject && personObject->nodesetval) {
xmlXPathObjectPtr personObject = xmlContext.getXpathObjectForNodeList(std::string(PresencePerson::pidfXmlPrefix));
if ((personObject != nullptr) && (personObject->nodesetval != nullptr)) {
for (int i = 1; i <= personObject->nodesetval->nodeNr; i++) {
ss.clear(), ss.str(std::string()), ss << PresencePerson::pidfXmlPrefix.data() << "[" << i << "]/@id";
std::string personIdStr = xmlContext.getTextContent(ss.str());
ss.clear(), ss.str(std::string()),
ss << PresencePerson::pidfXmlPrefix.data() << "[" << i << "]/dm:timestamp";
std::string personTimestampStr = xmlContext.getTextContent(ss.str());
if (!personTimestampStr.empty()) timestamp = PresenceModel::parseTimestamp(personTimestampStr);
if (!personTimestampStr.empty()) {
timestamp = PresenceModel::parseTimestamp(personTimestampStr);
}
std::shared_ptr<PresencePerson> person = PresencePerson::create(personIdStr, timestamp);
err = person->parsePidfXmlPresenceActivities(xmlContext, (unsigned int)i);
if (err == 0) err = person->parsePidfXmlPresenceNotes(xmlContext, (unsigned int)i);
if (err == 0) addPerson(person);
if (err != 0) break;
if (err == 0) {
err = person->parsePidfXmlPresencePermanentActivities(xmlContext, (unsigned int)i);
}
if (err == 0) {
err = person->parsePidfXmlPresenceNotes(xmlContext, (unsigned int)i);
}
if (err == 0) {
addPerson(person);
}
if (err != 0) {
break;
}
}
}
if (personObject) xmlXPathFreeObject(personObject);
if (personObject != nullptr) {
xmlXPathFreeObject(personObject);
}
if (err < 0) {
/* Remove all the persons added since there was an error. */
clearPersons();
@ -410,22 +473,30 @@ int PresenceModel::parsePidfXmlPresenceServices(XmlParsingContext &xmlContext) {
stringstream ss;
LinphonePresenceBasicStatus basicStatus;
xmlXPathObjectPtr serviceObject = xmlContext.getXpathObjectForNodeList(PresenceService::pidfXmlPrefix.data());
if (serviceObject && serviceObject->nodesetval) {
xmlXPathObjectPtr serviceObject = xmlContext.getXpathObjectForNodeList(std::string(PresenceService::pidfXmlPrefix));
if ((serviceObject != nullptr) && (serviceObject->nodesetval != nullptr)) {
for (int i = 1; i <= serviceObject->nodesetval->nodeNr; i++) {
ss.clear(), ss.str(std::string()),
ss << PresenceService::pidfXmlPrefix.data() << "[" << i << "]/pidf:status/pidf:basic";
std::string basicStatusStr = xmlContext.getTextContent(ss.str());
if (basicStatusStr.empty()) continue;
if (basicStatusStr == "open") basicStatus = LinphonePresenceBasicStatusOpen;
else if (basicStatusStr == "closed") basicStatus = LinphonePresenceBasicStatusClosed;
else return -1; /* Invalid value for basic status. */
if (basicStatusStr.empty()) {
continue;
}
if (basicStatusStr == "open") {
basicStatus = LinphonePresenceBasicStatusOpen;
} else if (basicStatusStr == "closed") {
basicStatus = LinphonePresenceBasicStatusClosed;
} else {
return -1; /* Invalid value for basic status. */
}
ss.clear(), ss.str(std::string()),
ss << PresenceService::pidfXmlPrefix.data() << "[" << i << "]/pidf:status/pidfonline:online";
xmlXPathObjectPtr pidfonlineObject = xmlContext.getXpathObjectForNodeList(ss.str());
if (pidfonlineObject) {
if (pidfonlineObject->nodesetval && pidfonlineObject->nodesetval->nodeNr > 0) mIsOnline = true;
if (pidfonlineObject != nullptr) {
if ((pidfonlineObject->nodesetval != nullptr) && pidfonlineObject->nodesetval->nodeNr > 0) {
mIsOnline = true;
}
xmlXPathFreeObject(pidfonlineObject);
}
@ -445,8 +516,8 @@ int PresenceModel::parsePidfXmlPresenceServices(XmlParsingContext &xmlContext) {
ss << PresenceService::pidfXmlPrefix.data() << "[" << i << "]/oma-pres:service-description";
xmlXPathObjectPtr descriptionsObject = xmlContext.getXpathObjectForNodeList(ss.str());
std::list<std::string> descriptions;
if (descriptionsObject) {
if (descriptionsObject->nodesetval) {
if (descriptionsObject != nullptr) {
if (descriptionsObject->nodesetval != nullptr) {
for (int j = 1; j <= descriptionsObject->nodesetval->nodeNr; j++) {
xmlContext.setXpathContextNode(xmlXPathNodeSetItem(descriptionsObject->nodesetval, j - 1));
std::string serviceId = xmlContext.getTextContent("./oma-pres:service-id");
@ -460,14 +531,22 @@ int PresenceModel::parsePidfXmlPresenceServices(XmlParsingContext &xmlContext) {
xmlXPathFreeObject(descriptionsObject);
}
if (!timestampStr.empty()) service->setTimestamp(PresenceModel::parseTimestamp(timestampStr));
if (!contactStr.empty()) service->setContact(contactStr);
if (!descriptions.empty()) service->setDescriptions(descriptions);
if (!timestampStr.empty()) {
service->setTimestamp(PresenceModel::parseTimestamp(timestampStr));
}
if (!contactStr.empty()) {
service->setContact(contactStr);
}
if (!descriptions.empty()) {
service->setDescriptions(descriptions);
}
service->parsePidfXmlPresenceNotes(xmlContext, (unsigned int)i);
addService(service);
}
}
if (serviceObject) xmlXPathFreeObject(serviceObject);
if (serviceObject != nullptr) {
xmlXPathFreeObject(serviceObject);
}
return 0;
}
@ -482,20 +561,28 @@ std::shared_ptr<PresenceNote> PresenceModel::findNoteWithLang(const std::string
const auto activityNoteIterator =
std::find_if(person->mActivitiesNotes.cbegin(), person->mActivitiesNotes.cend(),
[&](const auto &note) { return note->getLang() == lang; });
if (activityNoteIterator != person->mActivitiesNotes.cend()) return *activityNoteIterator;
if (activityNoteIterator != person->mActivitiesNotes.cend()) {
return *activityNoteIterator;
}
/* ... then look in the person notes. */
const auto noteIterator = std::find_if(person->mNotes.cbegin(), person->mNotes.cend(),
[&](const auto &note) { return note->getLang() == lang; });
if (noteIterator != person->mNotes.cend()) return *noteIterator;
if (noteIterator != person->mNotes.cend()) {
return *noteIterator;
}
}
for (const auto &service : mServices) {
const auto it = std::find_if(service->mNotes.cbegin(), service->mNotes.cend(),
[&](const auto &note) { return note->getLang() == lang; });
if (it != service->mNotes.cend()) return *it;
if (it != service->mNotes.cend()) {
return *it;
}
}
const auto noteIterator =
std::find_if(mNotes.cbegin(), mNotes.cend(), [&](const auto &note) { return note->getLang() == lang; });
if (noteIterator != mNotes.cend()) return *noteIterator;
if (noteIterator != mNotes.cend()) {
return *noteIterator;
}
return nullptr;
}
@ -519,12 +606,17 @@ std::string PresenceModel::toXml() const {
std::string content;
try {
if (!getPresentity())
if (!getPresentity()) {
throw PresenceModelXmlException("Cannot convert presence model to xml because no presentity set");
}
buf = xmlBufferCreate();
if (!buf) throw PresenceModelXmlException("Error creating the XML buffer");
if (buf == nullptr) {
throw PresenceModelXmlException("Error creating the XML buffer");
}
writer = xmlNewTextWriterMemory(buf, 0);
if (!writer) throw PresenceModelXmlException("Error creating the XML writer");
if (writer == nullptr) {
throw PresenceModelXmlException("Error creating the XML writer");
}
xmlTextWriterSetIndent(writer, 1);
int err = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", nullptr);
@ -556,18 +648,24 @@ std::string PresenceModel::toXml() const {
err = PresenceService::toXml(nullptr, writer, contact, false);
} else {
for (const auto &service : mServices) {
if (err >= 0) err = service->toXml(writer, contact, isOnline());
if (err >= 0) {
err = service->toXml(writer, contact, isOnline());
}
}
}
}
if (err >= 0) {
for (const auto &person : mPersons) {
if (err >= 0) err = person->toXml(writer);
if (err >= 0) {
err = person->toXml(writer);
}
}
}
if (err >= 0) {
for (const auto &note : mNotes) {
if (err >= 0) err = note->toXml(writer, "");
if (err >= 0) {
err = note->toXml(writer, "");
}
}
}
@ -586,8 +684,12 @@ std::string PresenceModel::toXml() const {
ms_error("%s", e.what());
}
if (writer) xmlFreeTextWriter(writer);
if (buf) xmlBufferFree(buf);
if (writer != nullptr) {
xmlFreeTextWriter(writer);
}
if (buf != nullptr) {
xmlBufferFree(buf);
}
return content;
}
@ -595,7 +697,7 @@ std::string PresenceModel::toXml() const {
std::string PresenceModel::toXml() const {
ms_warning("PresenceModel::toXml(): stubbed.");
return std::string();
return {};
}
#endif /* HAVE_XML2 */
@ -627,33 +729,35 @@ std::string PresenceModel::generatePresenceId() {
#ifdef HAVE_XML2
void PresenceModel::parsePresence(const std::string &contentType,
const std::string &contentSubtype,
const std::string &body,
SalPresenceModel **result) {
SalPresenceModel *PresenceModel::parsePresence(const std::string &contentType,
const std::string &contentSubtype,
const std::string &body) {
if (contentType != "application") {
*result = nullptr;
return;
return nullptr;
}
if (contentSubtype != "pidf+xml") {
*result = nullptr;
ms_error("Unknown content type '%s/%s' for presence", L_STRING_TO_C(contentType),
L_STRING_TO_C(contentSubtype));
return;
return nullptr;
}
std::shared_ptr<PresenceModel> model = nullptr;
XmlParsingContext xmlContext = XmlParsingContext(body);
if (xmlContext.isValid()) model = PresenceModel::parsePidfXmlPresence(xmlContext);
else ms_warning("Wrongly formatted presence XML: %s", xmlContext.getError().c_str());
if (xmlContext.isValid()) {
model = PresenceModel::parsePidfXmlPresence(xmlContext);
} else {
ms_warning("Wrongly formatted presence XML: %s", xmlContext.getError().c_str());
}
*result = (SalPresenceModel *)(model ? linphone_presence_model_ref(model->toC()) : nullptr);
return (SalPresenceModel *)(model ? linphone_presence_model_ref(model->toC()) : nullptr);
}
std::shared_ptr<PresenceModel> PresenceModel::parsePidfXmlPresence(XmlParsingContext &xmlContext) {
int err;
if (xmlContext.createXpathContext() < 0) return nullptr;
if (xmlContext.createXpathContext() < 0) {
return nullptr;
}
std::shared_ptr<PresenceModel> model = PresenceModel::create();
xmlXPathRegisterNs(xmlContext.getXpathContext(), reinterpret_cast<const xmlChar *>("pidf"),
@ -674,7 +778,9 @@ std::shared_ptr<PresenceModel> PresenceModel::parsePidfXmlPresence(XmlParsingCon
err = model->parsePidfXmlPresenceNotes(xmlContext);
}
if (err < 0) model = nullptr;
if (err < 0) {
model = nullptr;
}
return model;
}
@ -708,12 +814,11 @@ time_t PresenceModel::parseTimestamp(const std::string &timestamp) {
#else
void PresenceModel::parsePresence(BCTBX_UNUSED(const std::string &contentType),
BCTBX_UNUSED(const std::string &contentSubtype),
BCTBX_UNUSED(const std::string &body),
SalPresenceModel **result) {
if (result) *result = nullptr;
SalPresenceModel *PresenceModel::parsePresence(BCTBX_UNUSED(const std::string &contentType),
BCTBX_UNUSED(const std::string &contentSubtype),
BCTBX_UNUSED(const std::string &body)) {
ms_warning("PresenceModel::parsePresence(): stubbed.");
return nullptr;
}
#endif /* HAVE_XML2 */
@ -728,7 +833,9 @@ int PresenceModel::timestampToXml(xmlTextWriterPtr writer, time_t timestamp, con
err = xmlTextWriterWriteElementNS(writer, (const xmlChar *)L_STRING_TO_C(ns), (const xmlChar *)"timestamp",
nullptr, (const xmlChar *)timestamp_str);
}
if (timestamp_str) ms_free(timestamp_str);
if (timestamp_str != nullptr) {
ms_free(timestamp_str);
}
return err;
}
#endif /* HAVE_XML2 */

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -82,7 +82,7 @@ public:
std::shared_ptr<PresenceActivity> getActivity() const;
LinphonePresenceBasicStatus getBasicStatus() const;
int getCapabilities() const;
float getCapabilityVersion(const LinphoneFriendCapability capability) const;
float getCapabilityVersion(LinphoneFriendCapability capability) const;
LinphoneConsolidatedPresence getConsolidatedPresence() const;
const std::string &getContact() const;
time_t getLatestActivityTimestamp() const;
@ -106,26 +106,27 @@ public:
void clearNotes();
void clearPersons();
void clearServices();
bool hasCapability(const LinphoneFriendCapability capability) const;
bool hasCapabilityWithVersion(const LinphoneFriendCapability capability, float version) const;
bool hasCapabilityWithVersionOrMore(const LinphoneFriendCapability capability, float version) const;
bool hasCapability(LinphoneFriendCapability capability) const;
bool hasCapabilityWithVersion(LinphoneFriendCapability capability, float version) const;
bool hasCapabilityWithVersionOrMore(LinphoneFriendCapability capability, float version) const;
bool isOnline() const;
void hasBeenPublished();
#ifdef HAVE_XML2
int parsePidfXmlPresenceNotes(XmlParsingContext &xmlContext);
int parsePidfXmlPresencePersons(XmlParsingContext &xmlContext);
int parsePidfXmlPresenceServices(XmlParsingContext &xmlContext);
#endif /* HAVE_XML2 */
static SalPresenceModel *
parsePresence(const std::string &contentType, const std::string &contentSubtype, const std::string &body);
private:
std::shared_ptr<PresenceNote> findNoteWithLang(const std::string &lang) const;
std::string toXml() const;
static std::string basicStatusToString(const LinphonePresenceBasicStatus status);
static std::string basicStatusToString(LinphonePresenceBasicStatus status);
static std::string generatePresenceId();
static void parsePresence(const std::string &contentType,
const std::string &contentSubtype,
const std::string &body,
SalPresenceModel **result);
static time_t parseTimestamp(const std::string &timestamp);
#ifdef HAVE_XML2

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -47,8 +47,11 @@ PresencePerson *PresencePerson::clone() const {
// -----------------------------------------------------------------------------
void PresencePerson::setId(const std::string &id) {
if (id.empty()) mId = PresenceModel::generatePresenceId();
else mId = id;
if (id.empty()) {
mId = PresenceModel::generatePresenceId();
} else {
mId = id;
}
}
// -----------------------------------------------------------------------------
@ -69,7 +72,15 @@ unsigned int PresencePerson::getNbNotes() const {
return static_cast<unsigned int>(mNotes.size());
}
const std::shared_ptr<PresenceActivity> PresencePerson::getNthActivity(unsigned int idx) const {
unsigned int PresencePerson::getNbPermanentActivities() const {
return static_cast<unsigned int>(mPermanentActivities.size());
}
unsigned int PresencePerson::getNbPermanentActivitiesNotes() const {
return static_cast<unsigned int>(mPermanentActivitiesNotes.size());
}
std::shared_ptr<PresenceActivity> PresencePerson::getNthActivity(unsigned int idx) const {
try {
return mActivities.at(idx);
} catch (std::out_of_range &) {
@ -77,7 +88,7 @@ const std::shared_ptr<PresenceActivity> PresencePerson::getNthActivity(unsigned
}
}
const std::shared_ptr<PresenceNote> PresencePerson::getNthActivitiesNote(unsigned int idx) const {
std::shared_ptr<PresenceNote> PresencePerson::getNthActivitiesNote(unsigned int idx) const {
try {
return mActivitiesNotes.at(idx);
} catch (std::out_of_range &) {
@ -85,7 +96,7 @@ const std::shared_ptr<PresenceNote> PresencePerson::getNthActivitiesNote(unsigne
}
}
const std::shared_ptr<PresenceNote> PresencePerson::getNthNote(unsigned int idx) const {
std::shared_ptr<PresenceNote> PresencePerson::getNthNote(unsigned int idx) const {
try {
return mNotes.at(idx);
} catch (std::out_of_range &) {
@ -93,6 +104,22 @@ const std::shared_ptr<PresenceNote> PresencePerson::getNthNote(unsigned int idx)
}
}
std::shared_ptr<PresenceActivity> PresencePerson::getNthPermanentActivity(unsigned int idx) const {
try {
return mPermanentActivities.at(idx);
} catch (std::out_of_range &) {
return nullptr;
}
}
std::shared_ptr<PresenceNote> PresencePerson::getNthPermanentActivitiesNote(unsigned int idx) const {
try {
return mPermanentActivitiesNotes.at(idx);
} catch (std::out_of_range &) {
return nullptr;
}
}
time_t PresencePerson::getTimestamp() const {
return mTimestamp;
}
@ -100,24 +127,49 @@ time_t PresencePerson::getTimestamp() const {
// -----------------------------------------------------------------------------
LinphoneStatus PresencePerson::addActivity(const std::shared_ptr<PresenceActivity> &activity) {
if (activity == nullptr) return -1;
if (activity == nullptr) {
return -1;
}
// Insert in first position since its the most recent activity!
mActivities.insert(mActivities.cbegin(), activity);
return 0;
}
LinphoneStatus PresencePerson::addActivitiesNote(const std::shared_ptr<PresenceNote> &note) {
if (note == nullptr) return -1;
if (note == nullptr) {
return -1;
}
mNotes.insert(mActivitiesNotes.cbegin(), note);
return 0;
}
LinphoneStatus PresencePerson::addNote(const std::shared_ptr<PresenceNote> &note) {
if (note == nullptr) return -1;
if (note == nullptr) {
return -1;
}
mNotes.insert(mNotes.cbegin(), note);
return 0;
}
LinphoneStatus PresencePerson::addPermanentActivity(const std::shared_ptr<PresenceActivity> &activity) {
if (activity == nullptr) {
return -1;
}
// Insert in first position since its the most recent activity!
mPermanentActivities.insert(mPermanentActivities.cbegin(), activity);
mPermanentActivitiesToBePublished = true;
return 0;
}
LinphoneStatus PresencePerson::addPermanentActivitiesNote(const std::shared_ptr<PresenceNote> &note) {
if (note == nullptr) {
return -1;
}
mPermanentActivitiesNotes.insert(mPermanentActivitiesNotes.cbegin(), note);
mPermanentActivitiesToBePublished = true;
return 0;
}
void PresencePerson::clearActivities() {
mActivities.clear();
}
@ -130,6 +182,20 @@ void PresencePerson::clearNotes() {
mNotes.clear();
}
void PresencePerson::clearPermanentActivities() {
if (!mPermanentActivities.empty()) {
mPermanentActivities.clear();
mPermanentActivitiesToBePublished = true;
}
}
void PresencePerson::clearPermanentActivitiesNotes() {
if (!mPermanentActivitiesNotes.empty()) {
mPermanentActivitiesNotes.clear();
mPermanentActivitiesToBePublished = true;
}
}
bool PresencePerson::hasActivities() const {
return !mActivities.empty();
}
@ -142,6 +208,14 @@ bool PresencePerson::hasNotes() const {
return !mNotes.empty();
}
bool PresencePerson::hasPermanentActivities() const {
return !mPermanentActivities.empty();
}
bool PresencePerson::hasPermanentActivitiesNotes() const {
return !mPermanentActivitiesNotes.empty();
}
// -----------------------------------------------------------------------------
#ifdef HAVE_XML2
@ -150,36 +224,47 @@ int PresencePerson::parsePidfXmlPresenceActivities(XmlParsingContext &xmlContext
int err = 0;
ss << PresencePerson::pidfXmlPrefix.data() << "[" << personIdx << "]/rpid:activities";
xmlXPathObjectPtr activitiesNodesObject = xmlContext.getXpathObjectForNodeList(ss.str());
if (activitiesNodesObject && activitiesNodesObject->nodesetval) {
if ((activitiesNodesObject != nullptr) && (activitiesNodesObject->nodesetval != nullptr)) {
for (int i = 1; i <= activitiesNodesObject->nodesetval->nodeNr; i++) {
ss.clear(), ss.str(std::string()),
ss << PresencePerson::pidfXmlPrefix.data() << "[" << personIdx << "]/rpid:activities[" << i
<< "]/rpid:*";
xmlXPathObjectPtr activitiesObject = xmlContext.getXpathObjectForNodeList(ss.str());
if (activitiesObject && activitiesObject->nodesetval) {
if ((activitiesObject != nullptr) && (activitiesObject->nodesetval != nullptr)) {
for (int j = 0; j < activitiesObject->nodesetval->nodeNr; j++) {
xmlNodePtr activityNode = activitiesObject->nodesetval->nodeTab[j];
if (activityNode->name && PresenceActivity::isValidActivityName((const char *)activityNode->name)) {
if ((activityNode->name != nullptr) &&
PresenceActivity::isValidActivityName((const char *)activityNode->name)) {
LinphonePresenceActivityType activityType;
char *description = (char *)xmlNodeGetContent(activityNode);
if (description && (description[0] == '\0')) {
if ((description != nullptr) && (description[0] == '\0')) {
xmlFree(description);
description = nullptr;
}
err = PresenceActivity::activityNameToType((const char *)activityNode->name, &activityType);
if (err < 0) break;
if (err < 0) {
break;
}
std::shared_ptr<PresenceActivity> activity =
PresenceActivity::create(activityType, L_C_TO_STRING(description));
addActivity(activity);
if (description) xmlFree(description);
if (description != nullptr) {
xmlFree(description);
}
}
}
}
if (activitiesObject) xmlXPathFreeObject(activitiesObject);
if (err < 0) break;
if (activitiesObject != nullptr) {
xmlXPathFreeObject(activitiesObject);
}
if (err < 0) {
break;
}
}
}
if (activitiesNodesObject) xmlXPathFreeObject(activitiesNodesObject);
if (activitiesNodesObject != nullptr) {
xmlXPathFreeObject(activitiesNodesObject);
}
return err;
}
@ -188,13 +273,15 @@ int PresencePerson::parsePidfXmlPresenceNotes(XmlParsingContext &xmlContext, uns
stringstream ss;
ss << PresencePerson::pidfXmlPrefix.data() << "[" << personIdx << "]/rpid:activities/rpid:note";
xmlXPathObjectPtr noteObject = xmlContext.getXpathObjectForNodeList(ss.str());
if (noteObject && noteObject->nodesetval) {
if ((noteObject != nullptr) && (noteObject->nodesetval != nullptr)) {
for (int i = 1; i <= noteObject->nodesetval->nodeNr; i++) {
ss.clear(), ss.str(std::string()),
ss << PresencePerson::pidfXmlPrefix.data() << "[" << personIdx << "]/rpid:activities/rpid:note[" << i
<< "]";
std::string noteStr = xmlContext.getTextContent(ss.str());
if (noteStr.empty()) continue;
if (noteStr.empty()) {
continue;
}
ss.clear(), ss.str(std::string()),
ss << PresencePerson::pidfXmlPrefix.data() << "[" << personIdx << "]/rpid:activities/rpid:note[" << i
<< "]/@xml:lang";
@ -203,15 +290,42 @@ int PresencePerson::parsePidfXmlPresenceNotes(XmlParsingContext &xmlContext, uns
addActivitiesNote(note);
}
}
if (noteObject) xmlXPathFreeObject(noteObject);
if (noteObject != nullptr) {
xmlXPathFreeObject(noteObject);
}
ss.clear(), ss.str(std::string()),
ss << PresencePerson::pidfXmlPrefix.data() << "[" << personIdx << "]/rpid:permanent-activities/rpid:note";
noteObject = xmlContext.getXpathObjectForNodeList(ss.str());
if ((noteObject != nullptr) && (noteObject->nodesetval != nullptr)) {
for (int i = 1; i <= noteObject->nodesetval->nodeNr; i++) {
ss.clear(), ss.str(std::string()),
ss << PresencePerson::pidfXmlPrefix.data() << "[" << personIdx
<< "]/rpid:permanent-activities/rpid:note[" << i << "]";
std::string noteStr = xmlContext.getTextContent(ss.str());
if (noteStr.empty()) {
continue;
}
ss.clear(), ss.str(std::string()),
ss << PresencePerson::pidfXmlPrefix.data() << "[" << personIdx
<< "]/rpid:permanent-activities/rpid:note[" << i << "]/@xml:lang";
std::string lang = xmlContext.getTextContent(ss.str());
std::shared_ptr<PresenceNote> note = PresenceNote::create(noteStr, lang);
addPermanentActivitiesNote(note);
}
}
if (noteObject != nullptr) {
xmlXPathFreeObject(noteObject);
}
ss.clear(), ss.str(std::string()), ss << PresencePerson::pidfXmlPrefix.data() << "[" << personIdx << "]/dm:note";
noteObject = xmlContext.getXpathObjectForNodeList(ss.str());
if (noteObject && noteObject->nodesetval) {
if ((noteObject != nullptr) && (noteObject->nodesetval != nullptr)) {
for (int i = 1; i <= noteObject->nodesetval->nodeNr; i++) {
ss.clear(), ss.str(std::string()),
ss << PresencePerson::pidfXmlPrefix.data() << "[" << personIdx << "]/dm:note[" << i << "]";
std::string noteStr = xmlContext.getTextContent(ss.str());
if (noteStr.empty()) continue;
if (noteStr.empty()) {
continue;
}
ss.clear(), ss.str(std::string()),
ss << PresencePerson::pidfXmlPrefix.data() << "[" << personIdx << "]/dm:note[" << i << "]/@xml:lang";
std::string lang = xmlContext.getTextContent(ss.str());
@ -219,27 +333,85 @@ int PresencePerson::parsePidfXmlPresenceNotes(XmlParsingContext &xmlContext, uns
addNote(note);
}
}
if (noteObject) xmlXPathFreeObject(noteObject);
if (noteObject != nullptr) {
xmlXPathFreeObject(noteObject);
}
return 0;
}
int PresencePerson::parsePidfXmlPresencePermanentActivities(XmlParsingContext &xmlContext, unsigned int personIdx) {
stringstream ss;
int err = 0;
ss << PresencePerson::pidfXmlPrefix.data() << "[" << personIdx << "]/rpid:permanent-activities";
xmlXPathObjectPtr permanentActivitiesNodesObject = xmlContext.getXpathObjectForNodeList(ss.str());
if ((permanentActivitiesNodesObject != nullptr) && (permanentActivitiesNodesObject->nodesetval != nullptr)) {
for (int i = 1; i <= permanentActivitiesNodesObject->nodesetval->nodeNr; i++) {
ss.clear(), ss.str(std::string()),
ss << PresencePerson::pidfXmlPrefix.data() << "[" << personIdx << "]/rpid:permanent-activities[" << i
<< "]/rpid:*";
xmlXPathObjectPtr permanentActivitiesObject = xmlContext.getXpathObjectForNodeList(ss.str());
if ((permanentActivitiesObject != nullptr) && (permanentActivitiesObject->nodesetval != nullptr)) {
for (int j = 0; j < permanentActivitiesObject->nodesetval->nodeNr; j++) {
xmlNodePtr activityNode = permanentActivitiesObject->nodesetval->nodeTab[j];
if ((activityNode->name != nullptr) &&
PresenceActivity::isValidActivityName((const char *)activityNode->name)) {
LinphonePresenceActivityType activityType;
char *description = (char *)xmlNodeGetContent(activityNode);
if ((description != nullptr) && (description[0] == '\0')) {
xmlFree(description);
description = nullptr;
}
err = PresenceActivity::activityNameToType((const char *)activityNode->name, &activityType);
if (err < 0) {
break;
}
std::shared_ptr<PresenceActivity> activity =
PresenceActivity::create(activityType, L_C_TO_STRING(description));
addPermanentActivity(activity);
if (description != nullptr) {
xmlFree(description);
}
}
}
}
if (permanentActivitiesObject != nullptr) {
xmlXPathFreeObject(permanentActivitiesObject);
}
if (err < 0) {
break;
}
}
}
if (permanentActivitiesNodesObject != nullptr) {
xmlXPathFreeObject(permanentActivitiesNodesObject);
}
return err;
}
int PresencePerson::toXml(xmlTextWriterPtr writer) const {
int err = xmlTextWriterStartElementNS(writer, (const xmlChar *)"dm", (const xmlChar *)"person", nullptr);
if (err >= 0) {
std::string id = getId();
if (id.empty()) id = PresenceModel::generatePresenceId();
if (id.empty()) {
id = PresenceModel::generatePresenceId();
}
err = xmlTextWriterWriteAttribute(writer, (const xmlChar *)"id", (const xmlChar *)L_STRING_TO_C(id));
}
if ((err >= 0) && (hasActivitiesNotes() || hasActivities())) {
err = xmlTextWriterStartElementNS(writer, (const xmlChar *)"rpid", (const xmlChar *)"activities", nullptr);
if ((err >= 0) && hasActivitiesNotes()) {
for (const auto &note : mActivitiesNotes) {
if (err >= 0) err = note->toXml(writer, "rpid");
if (err >= 0) {
err = note->toXml(writer, "rpid");
}
}
}
if ((err >= 0) && hasActivities()) {
for (const auto &activity : mActivities) {
if (err >= 0) err = activity->toXml(writer);
if (err >= 0) {
err = activity->toXml(writer);
}
}
}
if (err >= 0) {
@ -247,9 +419,33 @@ int PresencePerson::toXml(xmlTextWriterPtr writer) const {
err = xmlTextWriterEndElement(writer);
}
}
if ((err >= 0) && mPermanentActivitiesToBePublished) {
err = xmlTextWriterStartElementNS(writer, (const xmlChar *)"rpid", (const xmlChar *)"permanent-activities",
nullptr);
if ((err >= 0) && hasPermanentActivitiesNotes()) {
for (const auto &note : mPermanentActivitiesNotes) {
if (err >= 0) {
err = note->toXml(writer, "rpid");
}
}
}
if ((err >= 0) && hasPermanentActivities()) {
for (const auto &activity : mPermanentActivities) {
if (err >= 0) {
err = activity->toXml(writer);
}
}
}
if (err >= 0) {
/* Close the "permanent-activities" element. */
err = xmlTextWriterEndElement(writer);
}
}
if ((err >= 0) && hasNotes()) {
for (const auto &note : mNotes) {
if (err >= 0) err = note->toXml(writer, "dm");
if (err >= 0) {
err = note->toXml(writer, "dm");
}
}
}
if (err >= 0) {

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -59,26 +59,37 @@ public:
unsigned int getNbActivities() const;
unsigned int getNbActivitiesNotes() const;
unsigned int getNbNotes() const;
const std::shared_ptr<PresenceActivity> getNthActivity(unsigned int idx) const;
const std::shared_ptr<PresenceNote> getNthActivitiesNote(unsigned int idx) const;
const std::shared_ptr<PresenceNote> getNthNote(unsigned int idx) const;
unsigned int getNbPermanentActivities() const;
unsigned int getNbPermanentActivitiesNotes() const;
std::shared_ptr<PresenceActivity> getNthActivity(unsigned int idx) const;
std::shared_ptr<PresenceNote> getNthActivitiesNote(unsigned int idx) const;
std::shared_ptr<PresenceNote> getNthNote(unsigned int idx) const;
std::shared_ptr<PresenceActivity> getNthPermanentActivity(unsigned int idx) const;
std::shared_ptr<PresenceNote> getNthPermanentActivitiesNote(unsigned int idx) const;
time_t getTimestamp() const;
// Other
LinphoneStatus addActivity(const std::shared_ptr<PresenceActivity> &activity);
LinphoneStatus addActivitiesNote(const std::shared_ptr<PresenceNote> &note);
LinphoneStatus addNote(const std::shared_ptr<PresenceNote> &note);
LinphoneStatus addPermanentActivity(const std::shared_ptr<PresenceActivity> &activity);
LinphoneStatus addPermanentActivitiesNote(const std::shared_ptr<PresenceNote> &note);
void clearActivities();
void clearActivitiesNotes();
void clearNotes();
void clearPermanentActivities();
void clearPermanentActivitiesNotes();
bool hasActivities() const;
bool hasActivitiesNotes() const;
bool hasNotes() const;
bool hasPermanentActivities() const;
bool hasPermanentActivitiesNotes() const;
private:
#ifdef HAVE_XML2
int parsePidfXmlPresenceActivities(XmlParsingContext &xmlContext, unsigned int personIdx);
int parsePidfXmlPresenceNotes(XmlParsingContext &xmlContext, unsigned int personIdx);
int parsePidfXmlPresencePermanentActivities(XmlParsingContext &xmlContext, unsigned int personIdx);
int toXml(xmlTextWriterPtr writer) const;
static constexpr std::string_view pidfXmlPrefix = "/pidf:presence/dm:person";
@ -89,6 +100,9 @@ private:
std::vector<std::shared_ptr<PresenceActivity>> mActivities;
std::vector<std::shared_ptr<PresenceNote>> mActivitiesNotes;
std::vector<std::shared_ptr<PresenceNote>> mNotes;
std::vector<std::shared_ptr<PresenceActivity>> mPermanentActivities;
std::vector<std::shared_ptr<PresenceNote>> mPermanentActivitiesNotes;
bool mPermanentActivitiesToBePublished = false;
};
LINPHONE_END_NAMESPACE

View file

@ -299,7 +299,6 @@ set(SOURCE_FILES_C
log_file_tester.c
message_tester.c
player_tester.c
presence_server_tester.c
presence_tester.c
proxy_config_tester.c
quality_reporting_tester.c
@ -363,6 +362,7 @@ set(SOURCE_FILES_CXX
local-secure-chat-tester.cpp
local-ephemeral-chat-tester.cpp
potential_configuration_tester.cpp
presence_server_tester.cpp
turn-server-tester.cpp
vcard_tester.cpp
vfs-encryption-tester.cpp

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2022 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
@ -18,6 +18,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "bctoolbox/charconv.h"
#include "bctoolbox/tester.h"
#include "belle-sip/object.h"
#include "liblinphone_tester.h"
#include "linphone/api/c-account-params.h"
#include "linphone/api/c-account.h"
@ -25,9 +28,12 @@
#include "linphone/api/c-dial-plan.h"
#include "linphone/api/c-friend.h"
#include "linphone/api/c-magic-search.h"
#include "linphone/api/c-presence.h"
#include "linphone/api/c-search-result.h"
#include "linphone/core.h"
#include "linphone/core_utils.h"
#include "linphone/types.h"
#include "presence/presence-model.h"
#include "private_functions.h"
#include "tester_utils.h"
// List coming from dialplan
@ -135,10 +141,10 @@ char *generate_random_e164_phone(void) {
!check_phone_country(atoi(linphone_dial_plan_get_country_calling_code(dialPlan))) ||
(strcmp("52", linphone_dial_plan_get_country_calling_code(dialPlan)) == 0))
; // fixme Linphone Mexican's dialplan has 2 ccc
belle_sip_object_remove_from_leak_detector((void *)genericDialPlan);
belle_sip_object_remove_from_leak_detector((belle_sip_object_t *)genericDialPlan);
belle_sip_object_remove_from_leak_detector(
(void *)dialPlan); // because mostCommon dial plan is a static object freed at the end of the process. This f is
// only to avoid wrong leak detection.
(belle_sip_object_t *)dialPlan); // because mostCommon dial plan is a static object freed at the end of the
// process. This f is only to avoid wrong leak detection.
return generate_random_e164_phone_from_dial_plan(dialPlan);
}
@ -501,6 +507,7 @@ static void test_presence_list_base(bool_t enable_compression) {
const char *pauline_identity;
bctbx_list_t *lcs = NULL;
LinphonePresenceModel *presence;
int previous_laure_number_of_NotifyPresenceReceived = 0;
enable_publish_verified(marie, TRUE);
enable_publish_verified(pauline, TRUE);
@ -634,7 +641,7 @@ static void test_presence_list_base(bool_t enable_compression) {
linphone_core_set_presence_model(marie->lc, presence);
linphone_presence_model_unref(presence);
int previous_laure_number_of_NotifyPresenceReceived = laure->stat.number_of_NotifyPresenceReceived;
previous_laure_number_of_NotifyPresenceReceived = laure->stat.number_of_NotifyPresenceReceived;
wait_for_list(lcs, &laure->stat.number_of_NotifyPresenceReceived,
previous_laure_number_of_NotifyPresenceReceived + 1, liblinphone_tester_sip_timeout);
BC_ASSERT_EQUAL(laure->stat.number_of_NotifyPresenceReceived, previous_laure_number_of_NotifyPresenceReceived + 1,
@ -809,6 +816,8 @@ static void test_presence_list_same_friend_two_addresses(void) {
LinphoneFriend *lf;
const char *marie_identity;
bctbx_list_t *lcs = NULL;
const LinphonePresenceModel *marie_presence_model = nullptr;
LinphoneConsolidatedPresence marie_presence = LinphoneConsolidatedPresenceOffline;
enable_publish_verified(marie, TRUE);
enable_publish_verified(pauline, TRUE);
@ -851,11 +860,9 @@ static void test_presence_list_same_friend_two_addresses(void) {
BC_ASSERT_EQUAL(linphone_friend_get_consolidated_presence(lf), LinphoneConsolidatedPresenceOnline, int, "%d");
if (!BC_ASSERT_TRUE(linphone_friend_is_presence_received(lf))) goto end;
const LinphonePresenceModel *marie_presence_model =
linphone_friend_get_presence_model_for_uri_or_tel(lf, marie_identity);
marie_presence_model = linphone_friend_get_presence_model_for_uri_or_tel(lf, marie_identity);
BC_ASSERT_PTR_NOT_NULL(marie_presence_model);
LinphoneConsolidatedPresence marie_presence =
linphone_presence_model_get_consolidated_presence(marie_presence_model);
marie_presence = linphone_presence_model_get_consolidated_presence(marie_presence_model);
BC_ASSERT_EQUAL(marie_presence, LinphoneConsolidatedPresenceBusy, int, "%d");
end:
@ -1010,19 +1017,19 @@ static void test_presence_list_subscribe_with_error(bool_t io_error) {
wait_for_until(laure->lc, pauline->lc, &laure->stat.number_of_LinphonePresenceActivityVacation, 2, 6000));
if (io_error) {
ms_message("Simulating socket error");
sal_set_recv_error(linphone_core_get_sal(laure->lc), -1);
linphone_core_get_sal(laure->lc)->setRecvError(-1);
wait_for_list(lcs, &dummy, 1, 500); /* just time for socket to be closed */
} else {
ms_message("Simulating in/out packets losses");
sal_set_send_error(linphone_core_get_sal(laure->lc),
1500); /*make sure no refresh is sent, trash the message without generating error*/
sal_set_recv_error(linphone_core_get_sal(laure->lc),
1500); /*make sure server notify to close the dialog is also ignored*/
linphone_core_get_sal(laure->lc)->setSendError(
1500); /*make sure no refresh is sent, trash the message without generating error*/
linphone_core_get_sal(laure->lc)->setRecvError(
1500); /*make sure server notify to close the dialog is also ignored*/
wait_for_list(lcs, &dummy, 1, 32000); /* Wait a little bit for the subscribe transaction to timeout */
}
/*restart normal behavior*/
sal_set_send_error(linphone_core_get_sal(laure->lc), 0);
sal_set_recv_error(linphone_core_get_sal(laure->lc), 1);
linphone_core_get_sal(laure->lc)->setSendError(0);
linphone_core_get_sal(laure->lc)->setRecvError(1);
/*a new subscribe should be sent */
BC_ASSERT_TRUE(
@ -1436,9 +1443,9 @@ static void long_term_presence_with_phone_without_sip(void) {
linphone_friend_unref(friend2);
belle_sip_object_remove_from_leak_detector(
(void *)dialPlan); // because mostCommon dial plan is a static object freed at the end of the process. This
// f is only to avoid wrong leak detection.
belle_sip_object_remove_from_leak_detector((void *)genericDialPlan);
(belle_sip_object_t *)dialPlan); // because mostCommon dial plan is a static object freed at the end of the
// process. This f is only to avoid wrong leak detection.
belle_sip_object_remove_from_leak_detector((belle_sip_object_t *)genericDialPlan);
linphone_core_manager_destroy(pauline);
bctbx_free(e164);
bctbx_free(phone);
@ -2426,8 +2433,8 @@ static void notify_friend_capabilities_after_publish(void) {
bctbx_list_t *lcs = NULL;
bctbx_list_t *specs = NULL;
specs = bctbx_list_append(specs, "groupchat/1.1");
specs = bctbx_list_append(specs, "lime/1.5");
specs = bctbx_list_append(specs, (char *)"groupchat/1.1");
specs = bctbx_list_append(specs, (char *)"lime/1.5");
linphone_core_set_linphone_specs_list(pauline->lc, specs);
bctbx_list_free(specs);
@ -2559,8 +2566,8 @@ static void notify_friend_capabilities_with_alias(void) {
marie = linphone_core_manager_create2("marie_rc", e164Marie);
linphone_config_set_bool(linphone_core_get_config(marie->lc), "lime", "enabled", FALSE);
linphone_core_set_user_agent(marie->lc, "full-presence-support-bypass", NULL);
specs = bctbx_list_append(specs, "groupchat/1.1");
specs = bctbx_list_append(specs, "lime/1.5");
specs = bctbx_list_append(specs, (char *)"groupchat/1.1");
specs = bctbx_list_append(specs, (char *)"lime/1.5");
linphone_core_set_linphone_specs_list(marie->lc, specs);
bctbx_list_free(specs);
linphone_core_manager_start(marie, TRUE);
@ -2613,9 +2620,9 @@ static void notify_friend_capabilities_with_alias(void) {
linphone_friend_unref(marieFriend);
linphone_friend_unref(laureFriend);
belle_sip_object_remove_from_leak_detector(
(void *)dialPlan); // because mostCommon dial plan is a static object freed at the end of the process.
// This f is only to avoid wrong leak detection.
belle_sip_object_remove_from_leak_detector((void *)genericDialPlan);
(belle_sip_object_t *)dialPlan); // because mostCommon dial plan is a static object freed at the end of the
// process. This f is only to avoid wrong leak detection.
belle_sip_object_remove_from_leak_detector((belle_sip_object_t *)genericDialPlan);
linphone_core_manager_destroy(pauline);
bctbx_free(e164Laure);
bctbx_free(phoneLaure);
@ -2648,8 +2655,8 @@ static void notify_search_result_capabilities_with_alias(void) {
marie = linphone_core_manager_create2("marie_rc", e164Marie);
linphone_core_set_user_agent(marie->lc, "full-presence-support-bypass", NULL);
specs = bctbx_list_append(specs, "groupchat/1.1");
specs = bctbx_list_append(specs, "lime/1.5");
specs = bctbx_list_append(specs, (char *)"groupchat/1.1");
specs = bctbx_list_append(specs, (char *)"lime/1.5");
linphone_core_set_linphone_specs_list(marie->lc, specs);
bctbx_list_free(specs);
linphone_core_manager_start(marie, TRUE);
@ -2679,7 +2686,7 @@ static void notify_search_result_capabilities_with_alias(void) {
if (BC_ASSERT_PTR_NOT_NULL(resultList)) {
BC_ASSERT_EQUAL((int)bctbx_list_size(resultList), 1, int, "%d");
const LinphoneSearchResult *sr = bctbx_list_nth_data(resultList, 0);
const auto *sr = reinterpret_cast<const LinphoneSearchResult *>(bctbx_list_nth_data(resultList, 0));
BC_ASSERT_TRUE(linphone_search_result_has_capability(sr, LinphoneFriendCapabilityGroupChat));
BC_ASSERT_TRUE(linphone_search_result_has_capability(sr, LinphoneFriendCapabilityLimeX3dh));
@ -2699,9 +2706,9 @@ static void notify_search_result_capabilities_with_alias(void) {
linphone_friend_unref(marieFriend);
belle_sip_object_remove_from_leak_detector(
(void *)dialPlan); // because mostCommon dial plan is a static object freed at the end of the process.
// This f is only to avoid wrong leak detection.
belle_sip_object_remove_from_leak_detector((void *)genericDialPlan);
(belle_sip_object_t *)dialPlan); // because mostCommon dial plan is a static object freed at the end of the
// process. This f is only to avoid wrong leak detection.
belle_sip_object_remove_from_leak_detector((belle_sip_object_t *)genericDialPlan);
linphone_core_manager_destroy(pauline);
bctbx_free(e164Marie);
@ -2713,6 +2720,181 @@ static void notify_search_result_capabilities_with_alias(void) {
}
#endif
static void enable_echoed_presence_subscription(LinphoneCoreManager *mgr) {
LinphoneAccountParams *account_params =
linphone_account_params_clone(linphone_account_get_params(linphone_core_get_default_account(mgr->lc)));
linphone_account_params_enable_echoed_presence_subscription(account_params, TRUE);
linphone_account_set_params(linphone_core_get_default_account(mgr->lc), account_params);
linphone_account_params_unref(account_params);
}
/*
* It is not possible to test a whole path of permanent activities since flexisip-presence does not handle it.
* Test only the SUBSCRIBE path here, by checking that we get a NOTIFY for the local identity when the echoed presence
* subscription is enabled. That validates that the local identity has correctly been added to the SUBSCRIBE.
* Also check that we have an echoed presence model after receiving the NOTIFY.
*/
void permanent_activities_subscribe() {
LinphoneCoreManager *laure = linphone_core_presence_manager_new("laure_tcp_rc");
LinphoneCoreManager *pauline = linphone_core_presence_manager_new(
transport_supported(LinphoneTransportTls) == TRUE ? "pauline_rc" : "pauline_tcp_rc");
linphone_core_set_user_agent(laure->lc, "bypass", nullptr);
linphone_core_set_user_agent(pauline->lc, "bypass", nullptr);
enable_echoed_presence_subscription(laure);
enable_publish_verified(laure, TRUE);
enable_publish_verified(pauline, TRUE);
const char *rls_uri = "sip:rls@sip.example.org";
const char *pauline_identity = get_identity(pauline);
bctbx_list_t *lcs = nullptr;
lcs = bctbx_list_append(lcs, laure->lc);
lcs = bctbx_list_append(lcs, pauline->lc);
LinphoneFriendList *lfl = linphone_core_create_friend_list(laure->lc);
linphone_friend_list_set_rls_uri(lfl, rls_uri);
LinphoneFriend *lf = linphone_core_create_friend_with_address(laure->lc, pauline_identity);
linphone_friend_list_add_friend(lfl, lf);
linphone_friend_unref(lf);
linphone_core_add_friend_list(laure->lc, lfl);
linphone_friend_list_update_subscriptions(lfl);
linphone_friend_list_unref(lfl);
wait_for_list(lcs, &laure->stat.number_of_NotifyPresenceReceived, 2, liblinphone_tester_sip_timeout);
BC_ASSERT_PTR_NOT_NULL(linphone_account_get_echoed_presence_model(linphone_core_get_default_account(laure->lc)));
bctbx_list_free(lcs);
linphone_core_manager_destroy(pauline);
linphone_core_manager_destroy(laure);
}
/*
* It is not possible to test a whole path of permanent activities since flexisip-presence does not handle it.
* Test only the PUBLISH path here, more precisely test the presence XML generation when adding and removing permanent
* activities.
*/
void permanent_activities_publish() {
LinphoneCoreManager *laure = linphone_core_manager_new("laure_tcp_rc");
LinphonePresenceModel *presence_model =
linphone_presence_model_new_with_activity(LinphonePresenceActivityDinner, nullptr);
linphone_presence_model_set_presentity(
presence_model, linphone_account_params_get_identity_address(
linphone_account_get_params(linphone_core_get_default_account(laure->lc))));
auto check_permanent_activities = [laure, presence_model](std::string lookup, bool expected_to_be_found) {
char *xml = linphone_presence_model_to_xml(presence_model);
if (expected_to_be_found) {
BC_ASSERT(std::string(xml).find(lookup) != std::string::npos);
} else {
BC_ASSERT(std::string(xml).find(lookup) == std::string::npos);
}
bctbx_free(xml), xml = nullptr;
linphone_account_set_presence_model(linphone_core_get_default_account(laure->lc), presence_model);
};
// Check that there is no permanent activities at the start.
check_permanent_activities("<rpid:permanent-activities>", false);
// Check that the permanent activities are present in the XML when adding one.
auto *activity = linphone_presence_activity_new(LinphonePresenceActivityAway, nullptr);
linphone_presence_person_add_permanent_activity(linphone_presence_model_get_nth_person(presence_model, 0),
activity);
linphone_presence_activity_unref(activity);
check_permanent_activities("<rpid:permanent-activities>", true);
// Check that modifying something other than the permanent activities in the model does not put the permanent
// activities section in the XML.
auto *person = linphone_presence_model_get_nth_person(presence_model, 0);
linphone_presence_person_clear_activities(person);
activity = linphone_presence_activity_new(LinphonePresenceActivityMeeting, nullptr);
linphone_presence_person_add_activity(person, activity);
linphone_presence_activity_unref(activity);
check_permanent_activities("<rpid:permanent-activities>", false);
// Check that the permanent activities are present in the XML when adding another one.
activity = linphone_presence_activity_new(LinphonePresenceActivityInTransit, nullptr);
linphone_presence_person_add_permanent_activity(linphone_presence_model_get_nth_person(presence_model, 0),
activity);
linphone_presence_activity_unref(activity);
check_permanent_activities("<rpid:permanent-activities>", true);
// Check that the permanent activities are present in the XML when adding a permanent activity note.
auto *note = linphone_presence_note_new("A simple note", "en");
linphone_presence_person_add_permanent_activities_note(linphone_presence_model_get_nth_person(presence_model, 0),
note);
linphone_presence_note_unref(note);
check_permanent_activities("<rpid:permanent-activities>", true);
// Check that the permanent activities section is still present in the XML when clearing the permanent activities,
// because the previously added permanent activity note is still there.
linphone_presence_person_clear_permanent_activities(linphone_presence_model_get_nth_person(presence_model, 0));
check_permanent_activities("<rpid:permanent-activities>", true);
// Check that the permanent activities section is present but empty in the XML when clearing the permanent
// activities notes.
linphone_presence_person_clear_permanent_activities_notes(
linphone_presence_model_get_nth_person(presence_model, 0));
check_permanent_activities("<rpid:permanent-activities/>", true);
// Check one more time that modifying something other than the permanent activities in the model does not put the
// permanent activities section in the XML.
linphone_presence_person_clear_activities(linphone_presence_model_get_nth_person(presence_model, 0));
check_permanent_activities("<rpid:permanent-activities>", false);
linphone_presence_model_unref(presence_model);
linphone_core_manager_destroy(laure);
}
/*
* It is not possible to test a whole path of permanent activities since flexisip-presence does not handle it.
* Test only the NOTIFY path here, more precisely test the presence XML with permanent activities parsing.
*/
void permanent_activities_notify() {
auto *presence_model = reinterpret_cast<LinphonePresenceModel *>(LinphonePrivate::PresenceModel::parsePresence(
"application", "pidf+xml",
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<presence xmlns:dm=\"urn:ietf:params:xml:ns:pidf:data-model\" xmlns:rpid=\"urn:ietf:params:xml:ns:pidf:rpid\" "
"entity=\"sip:laure_agmtzpl@sip.example.org\" xmlns=\"urn:ietf:params:xml:ns:pidf\">\n"
" <tuple id=\"ictb4s\">\n"
" <status>\n"
" <basic>open</basic>\n"
" </status>\n"
" <contact priority=\"0.8\">sip:laure_agmtzpl@sip.example.org</contact>\n"
" <timestamp>2026-07-09T14:21:48Z</timestamp>\n"
" </tuple>\n"
" <dm:person id=\"xtashx\">"
" <rpid:activities>\n"
" <rpid:meeting/>\n"
" </rpid:activities>\n"
" <rpid:permanent-activities>\n"
" <rpid:note xml:lang=\"en\">A simple note</rpid:note>\n"
" <rpid:in-transit/>\n"
" <rpid:away/>\n"
" </rpid:permanent-activities>\n"
" <dm:timestamp>2026-07-09T14:21:48Z</dm:timestamp>\n"
" </dm:person>\n"
"</presence>"));
BC_ASSERT_PTR_NOT_NULL(presence_model);
auto *person = linphone_presence_model_get_nth_person(presence_model, 0);
BC_ASSERT_PTR_NOT_NULL(person);
BC_ASSERT_EQUAL(linphone_presence_person_get_nb_permanent_activities(person), 2, int, "%d");
auto *activity = linphone_presence_person_get_nth_permanent_activity(person, 0);
BC_ASSERT_PTR_NOT_NULL(activity);
BC_ASSERT_EQUAL(linphone_presence_activity_get_type(activity), LinphonePresenceActivityAway, int, "%d");
activity = linphone_presence_person_get_nth_permanent_activity(person, 1);
BC_ASSERT_PTR_NOT_NULL(activity);
BC_ASSERT_EQUAL(linphone_presence_activity_get_type(activity), LinphonePresenceActivityInTransit, int, "%d");
BC_ASSERT_EQUAL(linphone_presence_person_get_nb_permanent_activities_notes(person), 1, int, "%d");
auto *note = linphone_presence_person_get_nth_permanent_activities_note(person, 0);
BC_ASSERT_PTR_NOT_NULL(note);
BC_ASSERT_STRING_EQUAL(linphone_presence_note_get_content(note), "A simple note");
BC_ASSERT_STRING_EQUAL(linphone_presence_note_get_lang(note), "en");
linphone_presence_model_unref(presence_model);
}
test_t presence_server_tests[] = {
TEST_NO_TAG("Simple Publish", simple_publish),
TEST_NO_TAG("Publish with 2 identities", publish_with_dual_identity),
@ -2767,11 +2949,14 @@ test_t presence_server_tests[] = {
TEST_ONE_TAG(
"Notify search result capabilities with alias", notify_search_result_capabilities_with_alias, "capabilities"),
#endif
TEST_ONE_TAG("Permanent activities SUBSCRIBE", permanent_activities_subscribe, "permanent-activities"),
TEST_ONE_TAG("Permanent activities PUBLISH", permanent_activities_publish, "permanent-activities"),
TEST_ONE_TAG("Permanent activities NOTIFY", permanent_activities_notify, "permanent-activities"),
};
test_suite_t presence_server_test_suite = {"Presence using server",
NULL,
NULL,
nullptr,
nullptr,
liblinphone_tester_before_each,
liblinphone_tester_after_each,
sizeof(presence_server_tests) / sizeof(presence_server_tests[0]),