Skip to content

Commit 5e3835b

Browse files
authored
Merge pull request #62 from mpretty-cyro/feature/custom-conflict-resolution
Added a mechanism to track when the public UserProfile data was last updated
2 parents 3ee705f + b0aa17d commit 5e3835b

File tree

11 files changed

+316
-15
lines changed

11 files changed

+316
-15
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ if(CCACHE_PROGRAM)
1717
endif()
1818

1919
project(libsession-util
20-
VERSION 1.5.4
20+
VERSION 1.5.5
2121
DESCRIPTION "Session client utility library"
2222
LANGUAGES ${LANGS})
2323

include/session/config/base.hpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,36 @@ class ConfigBase : public ConfigSig {
612612
return fallback;
613613
}
614614

615+
/// API: base/ConfigBase::DictFieldProxy::sys_time
616+
///
617+
/// Returns the integer value loaded into a seconds-since-epoch std::chrono::sys_seconds
618+
/// value if an integer value exists at the given location, std::nullopt otherwise.
619+
///
620+
/// Inputs: None
621+
///
622+
/// Outputs:
623+
/// - `std::optional<std::chrono::sys_time>` -- nullopt if the value doesn't exist (or isn't
624+
/// an integer), otherwise the integer value loaded as a seconds-from-epoch.
625+
std::optional<std::chrono::sys_seconds> sys_seconds() const {
626+
if (const auto* i = integer())
627+
return std::make_optional<std::chrono::sys_seconds>(std::chrono::seconds{*i});
628+
return std::nullopt;
629+
}
630+
631+
/// API: base/ConfigBase::DictFieldProxy::sys_time_or
632+
///
633+
/// Returns the value as a std::chrono::sys_time or a fallback if the value doesn't exist
634+
/// (or isn't an integer).
635+
///
636+
/// Inputs:
637+
/// - `fallback` -- this value will be returned if it the requested value doesn't exist
638+
///
639+
/// Outputs:
640+
/// - `int64_t` -- Returned Integer
641+
std::chrono::sys_seconds sys_seconds_or(std::chrono::sys_seconds fallback) const {
642+
return sys_seconds().value_or(fallback);
643+
}
644+
615645
/// API: base/ConfigBase::DictFieldProxy::set
616646
///
617647
/// Returns a const pointer to the set if one exists at the given location, nullptr
@@ -679,6 +709,19 @@ class ConfigBase : public ConfigSig {
679709
/// - `value` -- replaces current value with given integer
680710
void operator=(int64_t value) { assign_if_changed(value); }
681711

712+
/// API: base/ConfigBase::DictFieldProxy::operator=(std::chrono::sys_seconds)
713+
///
714+
/// Replaces the current value with an integer containing the seconds-since-epoch of the
715+
/// given system time point. This also auto-vivifies any intermediate dicts needed to reach
716+
/// the given key, including replacing non-dict values if they currently exist along the
717+
/// path.
718+
///
719+
/// Inputs:
720+
/// - `value` -- replaces current value with given sys_seconds's time_since_epoch() value.
721+
void operator=(std::chrono::sys_seconds value) {
722+
assign_if_changed(static_cast<int64_t>(value.time_since_epoch().count()));
723+
}
724+
682725
/// API: base/ConfigBase::DictFieldProxy::operator=(config::set)
683726
///
684727
/// Replaces the current value with the given set. This also auto-vivifies any

include/session/config/user_profile.h

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ LIBSESSION_EXPORT int user_profile_set_name(config_object* conf, const char* nam
9292
///
9393
/// Obtains the current profile pic. The pointers in the returned struct will be NULL if a profile
9494
/// pic is not currently set, and otherwise should be copied right away (they will not be valid
95-
/// beyond other API calls on this config object).
95+
/// beyond other API calls on this config object). The returned value will be the latest profile
96+
/// pic between when the user last set their profile and when it was last re-uploaded.
9697
///
9798
/// Declaration:
9899
/// ```cpp
@@ -110,7 +111,7 @@ LIBSESSION_EXPORT user_profile_pic user_profile_get_pic(const config_object* con
110111

111112
/// API: user_profile/user_profile_set_pic
112113
///
113-
/// Sets a user profile
114+
/// Sets a user profile pic
114115
///
115116
/// Declaration:
116117
/// ```cpp
@@ -128,6 +129,26 @@ LIBSESSION_EXPORT user_profile_pic user_profile_get_pic(const config_object* con
128129
/// - `int` -- Returns 0 on success, non-zero on error
129130
LIBSESSION_EXPORT int user_profile_set_pic(config_object* conf, user_profile_pic pic);
130131

132+
/// API: user_profile/user_profile_set_reupload_pic
133+
///
134+
/// Sets a user profile pic when reuploading
135+
///
136+
/// Declaration:
137+
/// ```cpp
138+
/// INT user_profile_set_reupload_pic(
139+
/// [in] config_object* conf,
140+
/// [in] user_profile_pic pic
141+
/// );
142+
/// ```
143+
///
144+
/// Inputs:
145+
/// - `conf` -- [in] Pointer to the config object
146+
/// - `pic` -- [in] Pointer to the pic
147+
///
148+
/// Outputs:
149+
/// - `int` -- Returns 0 on success, non-zero on error
150+
LIBSESSION_EXPORT int user_profile_set_reupload_pic(config_object* conf, user_profile_pic pic);
151+
131152
/// API: user_profile/user_profile_get_nts_priority
132153
///
133154
/// Gets the current note-to-self priority level. Will be negative for hidden, 0 for unpinned, and >
@@ -245,6 +266,19 @@ LIBSESSION_EXPORT int user_profile_get_blinded_msgreqs(const config_object* conf
245266
/// - `void` -- Returns Nothing
246267
LIBSESSION_EXPORT void user_profile_set_blinded_msgreqs(config_object* conf, int enabled);
247268

269+
/// API: user_profile/user_profile_get_profile_updated
270+
///
271+
/// Returns the timestamp that the user last updated their profile information; or `0` if it's
272+
/// never been updated. This value will return the latest timestamp between when the user last
273+
/// set their profile and when it was last re-uploaded.
274+
///
275+
/// Inputs: None
276+
///
277+
/// Outputs:
278+
/// - `int64_t` - timestamp (unix seconds) that the user last updated their public profile
279+
/// information. Will be `0` if it's never been updated.
280+
LIBSESSION_EXPORT int64_t user_profile_get_profile_updated(config_object* conf);
281+
248282
#ifdef __cplusplus
249283
} // extern "C"
250284
#endif

include/session/config/user_profile.hpp

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,19 @@ using namespace std::literals;
2424
/// M - set to 1 if blinded message request retrieval is enabled, 0 if retrieval is *disabled*, and
2525
/// omitted if the setting has not been explicitly set (or has been explicitly cleared for some
2626
/// reason).
27+
/// t - The unix timestamp (seconds) that the user last explicitly updated their profile information
28+
/// (automatically updates when changing `name`, `profile_pic` or `set_blinded_msgreqs`).
29+
/// P - user profile url after re-uploading (should take precedence over `p` when `T > t`).
30+
/// Q - user profile decryption key (binary) after re-uploading (should take precedence over `q`
31+
/// when `T > t`).
32+
/// T - The unix timestamp (seconds) that the user last re-uploaded their profile information
33+
/// (automatically updates when calling `set_reupload_profile_pic`).
2734

2835
class UserProfile : public ConfigBase {
2936

3037
public:
38+
friend class UserProfileTester;
39+
3140
// No default constructor
3241
UserProfile() = delete;
3342

@@ -101,7 +110,8 @@ class UserProfile : public ConfigBase {
101110
/// API: user_profile/UserProfile::get_profile_pic
102111
///
103112
/// Gets the user's current profile pic URL and decryption key. The returned object will
104-
/// evaluate as false if the URL and/or key are not set.
113+
/// evaluate as false if the URL and/or key are not set. The returned value will be the latest
114+
/// profile pic between when the user last set their profile and when it was last re-uploaded.
105115
///
106116
/// Inputs: None
107117
///
@@ -111,8 +121,8 @@ class UserProfile : public ConfigBase {
111121

112122
/// API: user_profile/UserProfile::set_profile_pic
113123
///
114-
/// Sets the user's current profile pic to a new URL and decryption key. Clears both if either
115-
/// one is empty.
124+
/// Sets the user's current profile pic to a new URL and decryption key. Clears both as well as
125+
/// the reupload values if either one is empty.
116126
///
117127
/// Declaration:
118128
/// ```cpp
@@ -129,6 +139,25 @@ class UserProfile : public ConfigBase {
129139
void set_profile_pic(std::string_view url, std::span<const unsigned char> key);
130140
void set_profile_pic(profile_pic pic);
131141

142+
/// API: user_profile/UserProfile::set_reupload_profile_pic
143+
///
144+
/// Sets the user's profile pic to a new URL and decryption key after reuploading.
145+
///
146+
/// Declaration:
147+
/// ```cpp
148+
/// void set_reupload_profile_pic(std::string_view url, std::span<const unsigned char> key);
149+
/// void set_reupload_profile_pic(profile_pic pic);
150+
/// ```
151+
///
152+
/// Inputs:
153+
/// - First function:
154+
/// - `url` -- URL pointing to the profile pic
155+
/// - `key` -- Decryption key
156+
/// - Second function:
157+
/// - `pic` -- Profile pic object
158+
void set_reupload_profile_pic(std::string_view url, std::span<const unsigned char> key);
159+
void set_reupload_profile_pic(profile_pic pic);
160+
132161
/// API: user_profile/UserProfile::get_nts_priority
133162
///
134163
/// Gets the Note-to-self conversation priority. Negative means hidden; 0 means unpinned;
@@ -199,6 +228,19 @@ class UserProfile : public ConfigBase {
199228
/// default).
200229
void set_blinded_msgreqs(std::optional<bool> enabled);
201230

231+
/// API: user_profile/UserProfile::get_profile_updated
232+
///
233+
/// Returns the timestamp that the user last updated their profile information; or `0` if it's
234+
/// never been updated. This value will return the latest timestamp between when the user last
235+
/// set their profile and when it was last re-uploaded.
236+
///
237+
/// Inputs: None
238+
///
239+
/// Outputs:
240+
/// - `std::chrono::sys_seconds` - timestamp that the user last updated their profile
241+
/// information. Will be `0` if it's never been updated.
242+
std::chrono::sys_seconds get_profile_updated() const;
243+
202244
bool accepts_protobuf() const override { return true; }
203245
};
204246

src/config.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,7 @@ ConfigMessage::ConfigMessage(
679679
for (const auto& [seqno_hash, ptrs] : replay) {
680680
const auto& [data, diff] = ptrs;
681681
apply_diff(data_, *diff, *data);
682+
682683
lagged_diffs_.emplace_hint(lagged_diffs_.end(), seqno_hash, *diff);
683684
}
684685

src/config/groups/keys.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1382,7 +1382,8 @@ std::pair<std::string, std::vector<unsigned char>> Keys::decrypt_message(
13821382
// The value we verify is the raw data *followed by* the group Ed25519 pubkey. (See the comment
13831383
// in encrypt_message).
13841384
assert(_sign_pk);
1385-
std::vector<unsigned char> to_verify(raw_data.size() + _sign_pk->size());
1385+
std::vector<unsigned char> to_verify;
1386+
to_verify.resize(raw_data.size() + _sign_pk->size());
13861387
std::memcpy(to_verify.data(), raw_data.data(), raw_data.size());
13871388
std::memcpy(to_verify.data() + raw_data.size(), _sign_pk->data(), _sign_pk->size());
13881389
if (0 != crypto_sign_ed25519_verify_detached(

src/config/internal.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <oxenc/bt_producer.h>
44

55
#include <cassert>
6+
#include <chrono>
67
#include <memory>
78
#include <string_view>
89
#include <type_traits>
@@ -180,6 +181,12 @@ std::optional<int64_t> maybe_int(const session::config::dict& d, const char* key
180181
// int. Equivalent to `maybe_int(d, key).value_or(0)`.
181182
int64_t int_or_0(const session::config::dict& d, const char* key);
182183

184+
// Returns std::chrono::system_clock::now(), with the given precision (seconds, if unspecified).
185+
template <typename Duration = std::chrono::seconds>
186+
std::chrono::sys_time<Duration> ts_now() {
187+
return std::chrono::floor<Duration>(std::chrono::system_clock::now());
188+
}
189+
183190
// Digs into a config `dict` to get out an int64_t containing unix timestamp seconds, returns it
184191
// wrapped in a std::chrono::sys_seconds. Returns nullopt if not there (or not int).
185192
std::optional<std::chrono::sys_seconds> maybe_ts(const session::config::dict& d, const char* key);

src/config/user_profile.cpp

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,24 @@ void UserProfile::set_name(std::string_view new_name) {
2828
if (new_name.size() > contact_info::MAX_NAME_LENGTH)
2929
throw std::invalid_argument{"Invalid profile name: exceeds maximum length"};
3030
set_nonempty_str(data["n"], new_name);
31+
32+
const auto target_timestamp = (data["t"].integer_or(0) >= data["T"].integer_or(0) ? "t" : "T");
33+
data[target_timestamp] = ts_now();
3134
}
3235
void UserProfile::set_name_truncated(std::string new_name) {
3336
set_name(utf8_truncate(std::move(new_name), contact_info::MAX_NAME_LENGTH));
3437
}
3538

3639
profile_pic UserProfile::get_profile_pic() const {
3740
profile_pic pic{};
38-
if (auto* url = data["p"].string(); url && !url->empty())
41+
42+
const bool use_primary_keys = (data["t"].integer_or(0) >= data["T"].integer_or(0));
43+
const auto url_key = (use_primary_keys ? "p" : "P");
44+
const auto key_key = (use_primary_keys ? "q" : "Q");
45+
46+
if (auto* url = data[url_key].string(); url && !url->empty())
3947
pic.url = *url;
40-
if (auto* key = data["q"].string(); key && key->size() == 32)
48+
if (auto* key = data[key_key].string(); key && key->size() == 32)
4149
pic.key.assign(
4250
reinterpret_cast<const unsigned char*>(key->data()),
4351
reinterpret_cast<const unsigned char*>(key->data()) + 32);
@@ -46,12 +54,28 @@ profile_pic UserProfile::get_profile_pic() const {
4654

4755
void UserProfile::set_profile_pic(std::string_view url, std::span<const unsigned char> key) {
4856
set_pair_if(!url.empty() && key.size() == 32, data["p"], url, data["q"], key);
57+
58+
// If the profile was removed then we should remove the "reupload" version as well
59+
if (url.empty() || key.size() != 32)
60+
set_reupload_profile_pic({});
61+
62+
data["t"] = ts_now();
4963
}
5064

5165
void UserProfile::set_profile_pic(profile_pic pic) {
5266
set_profile_pic(pic.url, pic.key);
5367
}
5468

69+
void UserProfile::set_reupload_profile_pic(
70+
std::string_view url, std::span<const unsigned char> key) {
71+
set_pair_if(!url.empty() && key.size() == 32, data["P"], url, data["Q"], key);
72+
data["T"] = ts_now();
73+
}
74+
75+
void UserProfile::set_reupload_profile_pic(profile_pic pic) {
76+
set_reupload_profile_pic(pic.url, pic.key);
77+
}
78+
5579
void UserProfile::set_nts_priority(int priority) {
5680
set_nonzero_int(data["+"], priority);
5781
}
@@ -75,6 +99,9 @@ void UserProfile::set_blinded_msgreqs(std::optional<bool> value) {
7599
data["M"].erase();
76100
else
77101
data["M"] = static_cast<int>(*value);
102+
103+
const auto target_timestamp = (data["t"].integer_or(0) >= data["T"].integer_or(0) ? "t" : "T");
104+
data[target_timestamp] = ts_now();
78105
}
79106

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

113+
std::chrono::sys_seconds UserProfile::get_profile_updated() const {
114+
if (auto t = data["t"].sys_seconds()) {
115+
if (auto T = data["T"].sys_seconds(); T && *T > *t)
116+
return *T;
117+
return *t;
118+
}
119+
return std::chrono::sys_seconds{};
120+
}
121+
86122
extern "C" {
87123

88124
using namespace session;
@@ -141,6 +177,21 @@ LIBSESSION_C_API int user_profile_set_pic(config_object* conf, user_profile_pic
141177
static_cast<int>(SESSION_ERR_BAD_VALUE));
142178
}
143179

180+
LIBSESSION_C_API int user_profile_set_reupload_pic(config_object* conf, user_profile_pic pic) {
181+
std::string_view url{pic.url};
182+
std::span<const unsigned char> key;
183+
if (!url.empty())
184+
key = {pic.key, 32};
185+
186+
return wrap_exceptions(
187+
conf,
188+
[&] {
189+
unbox<UserProfile>(conf)->set_reupload_profile_pic(url, key);
190+
return 0;
191+
},
192+
static_cast<int>(SESSION_ERR_BAD_VALUE));
193+
}
194+
144195
LIBSESSION_C_API int user_profile_get_nts_priority(const config_object* conf) {
145196
return unbox<UserProfile>(conf)->get_nts_priority();
146197
}
@@ -170,4 +221,8 @@ LIBSESSION_C_API void user_profile_set_blinded_msgreqs(config_object* conf, int
170221
unbox<UserProfile>(conf)->set_blinded_msgreqs(std::move(val));
171222
}
172223

173-
} // extern "C"
224+
LIBSESSION_C_API int64_t user_profile_get_profile_updated(config_object* conf) {
225+
return unbox<UserProfile>(conf)->get_profile_updated().time_since_epoch().count();
226+
}
227+
228+
} // extern "C"

0 commit comments

Comments
 (0)