Skip to content

[WIP] Added a mechanism to track when the public UserProfile data was last updated #62

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions include/session/config/user_profile.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ LIBSESSION_EXPORT int user_profile_set_name(config_object* conf, const char* nam
///
/// Obtains the current profile pic. The pointers in the returned struct will be NULL if a profile
/// pic is not currently set, and otherwise should be copied right away (they will not be valid
/// beyond other API calls on this config object).
/// beyond other API calls on this config object). The returned value will be the latest profile pic
/// between when the user last set their profile and when it was last re-uploaded.
///
/// Declaration:
/// ```cpp
Expand All @@ -110,7 +111,7 @@ LIBSESSION_EXPORT user_profile_pic user_profile_get_pic(const config_object* con

/// API: user_profile/user_profile_set_pic
///
/// Sets a user profile
/// Sets a user profile pic
///
/// Declaration:
/// ```cpp
Expand All @@ -128,6 +129,26 @@ LIBSESSION_EXPORT user_profile_pic user_profile_get_pic(const config_object* con
/// - `int` -- Returns 0 on success, non-zero on error
LIBSESSION_EXPORT int user_profile_set_pic(config_object* conf, user_profile_pic pic);

/// API: user_profile/user_profile_set_reupload_pic
///
/// Sets a user profile pic when reuploading
///
/// Declaration:
/// ```cpp
/// INT user_profile_set_reupload_pic(
/// [in] config_object* conf,
/// [in] user_profile_pic pic
/// );
/// ```
///
/// Inputs:
/// - `conf` -- [in] Pointer to the config object
/// - `pic` -- [in] Pointer to the pic
///
/// Outputs:
/// - `int` -- Returns 0 on success, non-zero on error
LIBSESSION_EXPORT int user_profile_set_reupload_pic(config_object* conf, user_profile_pic pic);

/// API: user_profile/user_profile_get_nts_priority
///
/// Gets the current note-to-self priority level. Will be negative for hidden, 0 for unpinned, and >
Expand Down Expand Up @@ -245,6 +266,19 @@ LIBSESSION_EXPORT int user_profile_get_blinded_msgreqs(const config_object* conf
/// - `void` -- Returns Nothing
LIBSESSION_EXPORT void user_profile_set_blinded_msgreqs(config_object* conf, int enabled);

/// API: user_profile/user_profile_get_profile_updated
///
/// Returns the timestamp that the user last updated their profile information; or `0` if it's
/// never been updated. This value will return the latest timestamp between when the user last
/// set their profile and when it was last re-uploaded.
///
/// Inputs: None
///
/// Outputs:
/// - `int64_t` - timestamp (unix seconds) that the user last updated their public profile
/// information. Will be `0` if it's never been updated.
LIBSESSION_EXPORT int64_t user_profile_get_profile_updated(config_object* conf);

#ifdef __cplusplus
} // extern "C"
#endif
46 changes: 43 additions & 3 deletions include/session/config/user_profile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ using namespace std::literals;
/// M - set to 1 if blinded message request retrieval is enabled, 0 if retrieval is *disabled*, and
/// omitted if the setting has not been explicitly set (or has been explicitly cleared for some
/// reason).
/// t - The unix timestamp (seconds) that the user last explicitly updated their profile information
/// (automatically updates when changing `name`, `profile_pic` or `set_blinded_msgreqs`).
/// P - user profile url after re-uploading (should take precedence over `p` when `T > t`).
/// Q - user profile decryption key (binary) after re-uploading (should take precedence over `q`
/// when `T > t`).
/// T - The unix timestamp (seconds) that the user last re-uploaded their profile information
/// (automatically updates when calling `set_reupload_profile_pic`).

class UserProfile : public ConfigBase {

Expand Down Expand Up @@ -101,7 +108,8 @@ class UserProfile : public ConfigBase {
/// API: user_profile/UserProfile::get_profile_pic
///
/// Gets the user's current profile pic URL and decryption key. The returned object will
/// evaluate as false if the URL and/or key are not set.
/// evaluate as false if the URL and/or key are not set. The returned value will be the latest
/// profile pic between when the user last set their profile and when it was last re-uploaded.
///
/// Inputs: None
///
Expand All @@ -111,8 +119,8 @@ class UserProfile : public ConfigBase {

/// API: user_profile/UserProfile::set_profile_pic
///
/// Sets the user's current profile pic to a new URL and decryption key. Clears both if either
/// one is empty.
/// Sets the user's current profile pic to a new URL and decryption key. Clears both as well as
/// the reupload values if either one is empty.
///
/// Declaration:
/// ```cpp
Expand All @@ -129,6 +137,25 @@ class UserProfile : public ConfigBase {
void set_profile_pic(std::string_view url, std::span<const unsigned char> key);
void set_profile_pic(profile_pic pic);

/// API: user_profile/UserProfile::set_reupload_profile_pic
///
/// Sets the user's profile pic to a new URL and decryption key after reuploading.
///
/// Declaration:
/// ```cpp
/// void set_reupload_profile_pic(std::string_view url, std::span<const unsigned char> key);
/// void set_reupload_profile_pic(profile_pic pic);
/// ```
///
/// Inputs:
/// - First function:
/// - `url` -- URL pointing to the profile pic
/// - `key` -- Decryption key
/// - Second function:
/// - `pic` -- Profile pic object
void set_reupload_profile_pic(std::string_view url, std::span<const unsigned char> key);
void set_reupload_profile_pic(profile_pic pic);

/// API: user_profile/UserProfile::get_nts_priority
///
/// Gets the Note-to-self conversation priority. Negative means hidden; 0 means unpinned;
Expand Down Expand Up @@ -199,6 +226,19 @@ class UserProfile : public ConfigBase {
/// default).
void set_blinded_msgreqs(std::optional<bool> enabled);

/// API: user_profile/UserProfile::get_profile_updated
///
/// Returns the timestamp that the user last updated their profile information; or `0` if it's
/// never been updated. This value will return the latest timestamp between when the user last
/// set their profile and when it was last re-uploaded.
///
/// Inputs: None
///
/// Outputs:
/// - `std::chrono::sys_seconds` - timestamp that the user last updated their profile
/// information. Will be `0` if it's never been updated.
std::chrono::sys_seconds get_profile_updated() const;

bool accepts_protobuf() const override { return true; }
};

Expand Down
1 change: 1 addition & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ ConfigMessage::ConfigMessage(
for (const auto& [seqno_hash, ptrs] : replay) {
const auto& [data, diff] = ptrs;
apply_diff(data_, *diff, *data);

lagged_diffs_.emplace_hint(lagged_diffs_.end(), seqno_hash, *diff);
}

Expand Down
59 changes: 57 additions & 2 deletions src/config/user_profile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,24 @@ void UserProfile::set_name(std::string_view new_name) {
if (new_name.size() > contact_info::MAX_NAME_LENGTH)
throw std::invalid_argument{"Invalid profile name: exceeds maximum length"};
set_nonempty_str(data["n"], new_name);

const auto target_timestamp = (data["T"].integer_or(0) > data["t"].integer_or(0) ? "T" : "t");
data[target_timestamp] = static_cast<int>(std::chrono::system_clock::now().time_since_epoch().count());
}
void UserProfile::set_name_truncated(std::string new_name) {
set_name(utf8_truncate(std::move(new_name), contact_info::MAX_NAME_LENGTH));
}

profile_pic UserProfile::get_profile_pic() const {
profile_pic pic{};
if (auto* url = data["p"].string(); url && !url->empty())

const bool use_primary_keys = (data["T"].integer_or(0) > data["t"].integer_or(0));
const auto url_key = (use_primary_keys ? "p" : "P");
const auto key_key = (use_primary_keys ? "q" : "Q");

if (auto* url = data[url_key].string(); url && !url->empty())
pic.url = *url;
if (auto* key = data["q"].string(); key && key->size() == 32)
if (auto* key = data[key_key].string(); key && key->size() == 32)
pic.key.assign(
reinterpret_cast<const unsigned char*>(key->data()),
reinterpret_cast<const unsigned char*>(key->data()) + 32);
Expand All @@ -46,12 +54,28 @@ profile_pic UserProfile::get_profile_pic() const {

void UserProfile::set_profile_pic(std::string_view url, std::span<const unsigned char> key) {
set_pair_if(!url.empty() && key.size() == 32, data["p"], url, data["q"], key);

// If the profile was removed then we should remove the "reupload" version as well
if (url.empty() || key.size() != 32) {
set_reupload_profile_pic({});
}

data["t"] = static_cast<int>(std::chrono::system_clock::now().time_since_epoch().count());
}

void UserProfile::set_profile_pic(profile_pic pic) {
set_profile_pic(pic.url, pic.key);
}

void UserProfile::set_reupload_profile_pic(std::string_view url, std::span<const unsigned char> key) {
set_pair_if(!url.empty() && key.size() == 32, data["P"], url, data["Q"], key);
data["T"] = static_cast<int>(std::chrono::system_clock::now().time_since_epoch().count());
}

void UserProfile::set_reupload_profile_pic(profile_pic pic) {
set_reupload_profile_pic(pic.url, pic.key);
}

void UserProfile::set_nts_priority(int priority) {
set_nonzero_int(data["+"], priority);
}
Expand All @@ -75,6 +99,9 @@ void UserProfile::set_blinded_msgreqs(std::optional<bool> value) {
data["M"].erase();
else
data["M"] = static_cast<int>(*value);

const auto target_timestamp = (data["T"].integer_or(0) > data["t"].integer_or(0) ? "T" : "t");
data[target_timestamp] = static_cast<int>(std::chrono::system_clock::now().time_since_epoch().count());
}

std::optional<bool> UserProfile::get_blinded_msgreqs() const {
Expand All @@ -83,6 +110,15 @@ std::optional<bool> UserProfile::get_blinded_msgreqs() const {
return std::nullopt;
}

std::chrono::sys_seconds UserProfile::get_profile_updated() const {
if (auto* t = data["t"].integer(); t) {
if (auto* T = data["T"].integer(); T && T > t)
return std::chrono::sys_seconds{std::chrono::seconds{*T}};
return std::chrono::sys_seconds{std::chrono::seconds{*t}};
}
return std::chrono::sys_seconds{};
}

extern "C" {

using namespace session;
Expand Down Expand Up @@ -141,6 +177,21 @@ LIBSESSION_C_API int user_profile_set_pic(config_object* conf, user_profile_pic
static_cast<int>(SESSION_ERR_BAD_VALUE));
}

LIBSESSION_C_API int user_profile_set_reupload_pic(config_object* conf, user_profile_pic pic) {
std::string_view url{pic.url};
std::span<const unsigned char> key;
if (!url.empty())
key = {pic.key, 32};

return wrap_exceptions(
conf,
[&] {
unbox<UserProfile>(conf)->set_reupload_profile_pic(url, key);
return 0;
},
static_cast<int>(SESSION_ERR_BAD_VALUE));
}

LIBSESSION_C_API int user_profile_get_nts_priority(const config_object* conf) {
return unbox<UserProfile>(conf)->get_nts_priority();
}
Expand Down Expand Up @@ -170,4 +221,8 @@ LIBSESSION_C_API void user_profile_set_blinded_msgreqs(config_object* conf, int
unbox<UserProfile>(conf)->set_blinded_msgreqs(std::move(val));
}

LIBSESSION_C_API int64_t user_profile_get_profile_updated(config_object* conf) {
return unbox<UserProfile>(conf)->get_profile_updated().time_since_epoch().count();
}

} // extern "C"