From 547bcab162f370e596ba6b9eca157dbe9eb15ed7 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Mon, 28 Jul 2025 16:35:12 -0300 Subject: [PATCH] Type-safe timestamps values Taking raw integers has led to clients passing invalid values in past releases (milliseconds or microseconds). This is, however, quite damaging as it means there are *three* possible representations of the same config, depending on which of s/ms/us gets stored, and while we have patched around those issues, there is still a type safety concern as it could happen again. This fixes it so that we *always* store timestamps as std::chrono::sys_seconds making it completely unambiguous what it holds, and ensuring that the proper values gets passed in (either through direct construction, or by calling session::to_sys_seconds to explicitly guess based on the magnitude). This should keep any milliseconds or microseconds usage strictly within the API and make it very difficult for such an erroneous value to end up inside the actual serialized config. This also updates volatile last_read in a similar way: but since that one is always unix epoch milliseconds, using a std::chrono::sys_time (typedefed to session::sys_milliseconds) for the type safety. Unlike the above, this one has never had ambiguous values and so doesn't need the same heuristics. Making it typesafe makes it stay that way. --- CMakeLists.txt | 2 +- include/session/config/contacts.h | 2 +- include/session/config/contacts.hpp | 18 +++- .../session/config/convo_info_volatile.hpp | 2 +- include/session/config/groups/info.hpp | 41 ++++---- include/session/config/user_groups.h | 1 - include/session/config/user_groups.hpp | 10 +- include/session/util.hpp | 14 +-- src/config/contacts.cpp | 25 +++-- src/config/convo_info_volatile.cpp | 48 ++++------ src/config/groups/info.cpp | 59 ++++++++---- src/config/user_groups.cpp | 24 ++--- tests/test_config_contacts.cpp | 12 +-- tests/test_config_convo_info_volatile.cpp | 50 +++++----- tests/test_config_user_groups.cpp | 96 ++++++++++--------- tests/test_group_info.cpp | 28 +++--- 16 files changed, 243 insertions(+), 189 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c71fee55..3b4f2e7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ if(CCACHE_PROGRAM) endif() project(libsession-util - VERSION 1.5.1 + VERSION 1.6.0 DESCRIPTION "Session client utility library" LANGUAGES ${LANGS}) diff --git a/include/session/config/contacts.h b/include/session/config/contacts.h index 08c47fbc..e71b1bcf 100644 --- a/include/session/config/contacts.h +++ b/include/session/config/contacts.h @@ -28,7 +28,7 @@ typedef struct contacts_contact { int priority; CONVO_NOTIFY_MODE notifications; - int64_t mute_until; + int64_t mute_until; // unix timestamp (seconds) CONVO_EXPIRATION_MODE exp_mode; int exp_seconds; diff --git a/include/session/config/contacts.hpp b/include/session/config/contacts.hpp index 8f38ef06..1d80e014 100644 --- a/include/session/config/contacts.hpp +++ b/include/session/config/contacts.hpp @@ -82,12 +82,13 @@ struct contact_info { // conversation is hidden. Otherwise (0) this is a regular, unpinned // conversation. notify_mode notifications = notify_mode::defaulted; - int64_t mute_until = 0; // If non-zero, disable notifications until the given unix timestamp - // (seconds, overriding whatever the current `notifications` value is - // until the timestamp expires). + std::chrono::sys_seconds mute_until{0s}; // If timestamp is non-zero, disable notifications + // until the given unix timestamp (seconds, overriding + // whatever the current `notifications` value is until + // the timestamp expires). expiration_mode exp_mode = expiration_mode::none; // The expiry time; none if not expiring. std::chrono::seconds exp_timer{0}; // The expiration timer (in seconds) - int64_t created = 0; // Unix timestamp (seconds) when this contact was added + std::chrono::sys_seconds created{0s}; // Unix timestamp (seconds) when this contact was added explicit contact_info(std::string sid); @@ -384,7 +385,14 @@ class Contacts : public ConfigBase { /// Inputs: /// - `session_id` -- hex string of the session id /// - `timestamp` -- standard unix timestamp of the time contact was created - void set_created(std::string_view session_id, int64_t timestamp); + void set_created(std::string_view session_id, std::chrono::sys_seconds timestamp); + + /// Deprecated: takes timestamp as an integer and guess whether it is seconds, milliseconds, or + /// microseconds. + [[deprecated( + "pass a std::chrono::sys_seconds instead (perhaps using " + "session::to_sys_seconds)")]] void + set_created(std::string_view session_id, int64_t timestamp); /// API: contacts/contacts::erase /// diff --git a/include/session/config/convo_info_volatile.hpp b/include/session/config/convo_info_volatile.hpp index ce954ddb..a713b8e2 100644 --- a/include/session/config/convo_info_volatile.hpp +++ b/include/session/config/convo_info_volatile.hpp @@ -67,7 +67,7 @@ class val_loader; namespace convo { struct base { - int64_t last_read = 0; + sys_milliseconds last_read{}; bool unread = false; protected: diff --git a/include/session/config/groups/info.hpp b/include/session/config/groups/info.hpp index 64c2e48f..8d405560 100644 --- a/include/session/config/groups/info.hpp +++ b/include/session/config/groups/info.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include "../base.hpp" @@ -220,7 +219,11 @@ class Info : public ConfigBase { /// Inputs: /// - `session_id` -- hex string of the session id /// - `timestamp` -- standard unix timestamp when the group was created - void set_created(int64_t timestamp); + void set_created(std::chrono::sys_seconds timestamp); + /// Deprecated version that attempts to guess whether the input is seconds, milliseconds, or + /// microseconds. + [[deprecated("pass a std::chrono::sys_seconds instead (or use session::to_sys_seconds)")]] void + set_created(int64_t timestamp); /// API: groups/Info::get_created /// @@ -229,9 +232,9 @@ class Info : public ConfigBase { /// Inputs: none. /// /// Outputs: - /// - `std::optional` -- the unix timestamp when the group was created, or nullopt if - /// the creation timestamp is not set. - std::optional get_created() const; + /// - `std::chrono::sys_seconds` -- the unix timestamp when the group was + /// created, or nullopt if the creation timestamp is not set. + std::optional get_created() const; /// API: groups/Info::set_delete_before /// @@ -239,13 +242,14 @@ class Info : public ConfigBase { /// the closed group history with a timestamp earlier than this value. Returns nullopt if no /// delete-before timestamp is set. /// - /// The given value is checked for sanity (e.g. if you pass milliseconds it will be - /// interpreted as such) - /// /// Inputs: /// - `timestamp` -- the new unix timestamp before which clients should delete messages. Pass 0 /// (or negative) to disable the delete-before timestamp. - void set_delete_before(int64_t timestamp); + void set_delete_before(std::chrono::sys_seconds timestamp); + /// Deprecated version that attempts to guess whether you meant seconds, milliseconds, or + /// microseconds. + [[deprecated("pass a std::chrono::sys_seconds instead (or use session::to_sys_seconds)")]] void + set_delete_before(int64_t timestamp); /// API: groups/Info::get_delete_before /// @@ -257,8 +261,9 @@ class Info : public ConfigBase { /// Inputs: none. /// /// Outputs: - /// - `int64_t` -- the unix timestamp for which all older messages shall be delete - std::optional get_delete_before() const; + /// - `sys_seconds` -- the unix timestamp for which all older messages shall be deleted, or + /// nullopt if there is no delete-before timestamp set. + std::optional get_delete_before() const; /// API: groups/Info::set_delete_attach_before /// @@ -272,9 +277,12 @@ class Info : public ConfigBase { /// /// Inputs: /// - `timestamp` -- the new unix timestamp before which clients should delete attachments. Pass - /// 0 - /// (or negative) to disable the delete-attachment-before timestamp. - void set_delete_attach_before(int64_t timestamp); + /// 0 (or negative) to disable the delete-attachment-before timestamp. + void set_delete_attach_before(std::chrono::sys_seconds timestamp); + /// Deprecated version that attempts to guess whether you meant seconds, milliseconds, or + /// microseconds. + [[deprecated("pass a std::chrono::sys_seconds instead (or use session::to_sys_seconds)")]] void + set_delete_attach_before(int64_t timestamp); /// API: groups/Info::get_delete_attach_before /// @@ -286,8 +294,9 @@ class Info : public ConfigBase { /// Inputs: none. /// /// Outputs: - /// - `int64_t` -- the unix timestamp for which all older message attachments shall be deleted - std::optional get_delete_attach_before() const; + /// - `sys_seconds` -- the unix timestamp for which all older message attachments shall be + /// deleted, or nullopt if delete-attach-before is not enabled. + std::optional get_delete_attach_before() const; /// API: groups/Info::destroy_group /// diff --git a/include/session/config/user_groups.h b/include/session/config/user_groups.h index ef279c28..48e470a6 100644 --- a/include/session/config/user_groups.h +++ b/include/session/config/user_groups.h @@ -6,7 +6,6 @@ extern "C" { #include "base.h" #include "notify.h" -#include "util.h" // Maximum length of a group name, in bytes LIBSESSION_EXPORT extern const size_t GROUP_NAME_MAX_LENGTH; diff --git a/include/session/config/user_groups.hpp b/include/session/config/user_groups.hpp index 55febbc9..057c6994 100644 --- a/include/session/config/user_groups.hpp +++ b/include/session/config/user_groups.hpp @@ -82,11 +82,13 @@ namespace session::config { struct base_group_info { static constexpr size_t NAME_MAX_LENGTH = 100; // in bytes; name will be truncated if exceeded - int priority = 0; // The priority; 0 means unpinned, -1 means hidden, positive means - // pinned higher (i.e. higher priority conversations come first). - int64_t joined_at = 0; // unix timestamp (seconds) when the group was joined (or re-joined) + int priority = 0; // The priority; 0 means unpinned, -1 means hidden, positive means + // pinned higher (i.e. higher priority conversations come first). + std::chrono::sys_seconds joined_at{}; // unix timestamp (seconds) when the group + // was joined (or re-joined) notify_mode notifications = notify_mode::defaulted; // When the user wants notifications - int64_t mute_until = 0; // unix timestamp (seconds) until which notifications are disabled + std::chrono::sys_seconds mute_until{}; // unix timestamp (seconds) until which + // notifications are disabled std::string name; // human-readable; always set for a legacy closed group, only used before // joining a new closed group (after joining the group info provide the name) diff --git a/include/session/util.hpp b/include/session/util.hpp index fc57908f..9cd6d452 100644 --- a/include/session/util.hpp +++ b/include/session/util.hpp @@ -249,13 +249,7 @@ inline std::string utf8_truncate(std::string val, size_t n) { return val; } -// Helper function to transform a timestamp provided in seconds, milliseconds or microseconds to -// seconds -inline int64_t to_epoch_seconds(int64_t timestamp) { - return timestamp > 9'000'000'000'000 ? timestamp / 1'000'000 - : timestamp > 9'000'000'000 ? timestamp / 1'000 - : timestamp; -} +using sys_milliseconds = std::chrono::sys_time; // Takes a timestamp as unix epoch seconds (not ms, µs) and wraps it in a sys_seconds containing it. inline std::chrono::sys_seconds as_sys_seconds(int64_t timestamp) { @@ -276,4 +270,10 @@ static_assert(std::is_same_v< std::chrono::seconds, decltype(std::declval().time_since_epoch())>); +// Takes a timestamp as unix epoch milliseconds (not seconds, or microseconds) and wraps it in a +// sys_ms containing it. +inline sys_milliseconds as_sys_ms(int64_t timestamp) { + return sys_milliseconds{std::chrono::milliseconds{timestamp}}; +} + } // namespace session diff --git a/src/config/contacts.cpp b/src/config/contacts.cpp index 5c1be6b4..2f52f972 100644 --- a/src/config/contacts.cpp +++ b/src/config/contacts.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -92,7 +93,9 @@ void contact_info::load(const dict& info_dict) { } else { notifications = notify_mode::defaulted; } - mute_until = to_epoch_seconds(int_or_0(info_dict, "!")); + // Older client versions might have accidentally stored this as ms, so run it through + // to_sys_seconds: + mute_until = to_sys_seconds(int_or_0(info_dict, "!")); int exp_mode_ = int_or_0(info_dict, "e"); if (exp_mode_ >= static_cast(expiration_mode::none) && @@ -113,7 +116,9 @@ void contact_info::load(const dict& info_dict) { } } - created = to_epoch_seconds(int_or_0(info_dict, "j")); + // Older client versions might have accidentally stored this as ms, so run it through + // to_sys_seconds: + created = to_sys_seconds(int_or_0(info_dict, "j")); } void contact_info::into(contacts_contact& c) const { @@ -132,12 +137,12 @@ void contact_info::into(contacts_contact& c) const { c.blocked = blocked; c.priority = priority; c.notifications = static_cast(notifications); - c.mute_until = to_epoch_seconds(mute_until); + c.mute_until = mute_until.time_since_epoch().count(); c.exp_mode = static_cast(exp_mode); c.exp_seconds = exp_timer.count(); if (c.exp_seconds <= 0 && c.exp_mode != CONVO_EXPIRATION_NONE) c.exp_mode = CONVO_EXPIRATION_NONE; - c.created = to_epoch_seconds(created); + c.created = created.time_since_epoch().count(); } contact_info::contact_info(const contacts_contact& c) : session_id{c.session_id, 66} { @@ -156,12 +161,12 @@ contact_info::contact_info(const contacts_contact& c) : session_id{c.session_id, blocked = c.blocked; priority = c.priority; notifications = static_cast(c.notifications); - mute_until = to_epoch_seconds(c.mute_until); + mute_until = to_sys_seconds(c.mute_until); exp_mode = static_cast(c.exp_mode); exp_timer = exp_mode == expiration_mode::none ? 0s : std::chrono::seconds{c.exp_seconds}; if (exp_timer <= 0s && exp_mode != expiration_mode::none) exp_mode = expiration_mode::none; - created = to_epoch_seconds(c.created); + created = to_sys_seconds(c.created); } std::optional Contacts::get(std::string_view pubkey_hex) const { @@ -211,7 +216,7 @@ void Contacts::set(const contact_info& contact) { if (notify == notify_mode::mentions_only) notify = notify_mode::all; set_positive_int(info["@"], static_cast(notify)); - set_positive_int(info["!"], to_epoch_seconds(contact.mute_until)); + set_ts(info["!"], contact.mute_until); set_pair_if( contact.exp_mode != expiration_mode::none && contact.exp_timer > 0s, @@ -220,7 +225,7 @@ void Contacts::set(const contact_info& contact) { info["E"], contact.exp_timer.count()); - set_positive_int(info["j"], to_epoch_seconds(contact.created)); + set_ts(info["j"], contact.created); } void Contacts::set_name(std::string_view session_id, std::string name) { @@ -285,9 +290,9 @@ void Contacts::set_expiry( set(c); } -void Contacts::set_created(std::string_view session_id, int64_t timestamp) { +void Contacts::set_created(std::string_view session_id, std::chrono::sys_seconds timestamp) { auto c = get_or_construct(session_id); - c.created = to_epoch_seconds(timestamp); + c.created = timestamp; set(c); } diff --git a/src/config/convo_info_volatile.cpp b/src/config/convo_info_volatile.cpp index 54f73b62..cfd6e018 100644 --- a/src/config/convo_info_volatile.cpp +++ b/src/config/convo_info_volatile.cpp @@ -5,14 +5,10 @@ #include #include -#include -#include -#include #include #include "internal.hpp" #include "session/config/convo_info_volatile.h" -#include "session/config/error.h" #include "session/export.h" #include "session/types.hpp" #include "session/util.hpp" @@ -30,17 +26,17 @@ namespace convo { check_session_id(session_id); } one_to_one::one_to_one(const convo_info_volatile_1to1& c) : - base{c.last_read, c.unread}, session_id{c.session_id, 66} {} + base{as_sys_ms(c.last_read), c.unread}, session_id{c.session_id, 66} {} void one_to_one::into(convo_info_volatile_1to1& c) const { std::memcpy(c.session_id, session_id.data(), 67); - c.last_read = last_read; + c.last_read = last_read.time_since_epoch().count(); c.unread = unread; } community::community(const convo_info_volatile_community& c) : config::community{c.base_url, c.room, std::span{c.pubkey, 32}}, - base{c.last_read, c.unread} {} + base{as_sys_ms(c.last_read), c.unread} {} void community::into(convo_info_volatile_community& c) const { static_assert(sizeof(c.base_url) == BASE_URL_MAX_LENGTH + 1); @@ -48,7 +44,7 @@ namespace convo { copy_c_str(c.base_url, base_url()); copy_c_str(c.room, room_norm()); std::memcpy(c.pubkey, pubkey().data(), 32); - c.last_read = last_read; + c.last_read = last_read.time_since_epoch().count(); c.unread = unread; } @@ -59,11 +55,11 @@ namespace convo { check_session_id(id, "03"); } group::group(const convo_info_volatile_group& c) : - base{c.last_read, c.unread}, id{c.group_id, 66} {} + base{as_sys_ms(c.last_read), c.unread}, id{c.group_id, 66} {} void group::into(convo_info_volatile_group& c) const { std::memcpy(c.group_id, id.c_str(), 67); - c.last_read = last_read; + c.last_read = last_read.time_since_epoch().count(); c.unread = unread; } @@ -74,11 +70,11 @@ namespace convo { check_session_id(id); } legacy_group::legacy_group(const convo_info_volatile_legacy_group& c) : - base{c.last_read, c.unread}, id{c.group_id, 66} {} + base{as_sys_ms(c.last_read), c.unread}, id{c.group_id, 66} {} void legacy_group::into(convo_info_volatile_legacy_group& c) const { std::memcpy(c.group_id, id.data(), 67); - c.last_read = last_read; + c.last_read = last_read.time_since_epoch().count(); c.unread = unread; } @@ -91,19 +87,19 @@ namespace convo { check_session_id(blinded_session_id, legacy_blinding ? "15" : "25"); } blinded_one_to_one::blinded_one_to_one(const convo_info_volatile_blinded_1to1& c) : - base{c.last_read, c.unread}, + base{as_sys_ms(c.last_read), c.unread}, blinded_session_id{c.blinded_session_id, 66}, legacy_blinding{c.legacy_blinding} {} void blinded_one_to_one::into(convo_info_volatile_blinded_1to1& c) const { std::memcpy(c.blinded_session_id, blinded_session_id.data(), 67); - c.last_read = last_read; + c.last_read = last_read.time_since_epoch().count(); c.unread = unread; c.legacy_blinding = legacy_blinding; } void base::load(const dict& info_dict) { - last_read = int_or_0(info_dict, "r"); + last_read = as_sys_ms(int_or_0(info_dict, "r")); unread = (bool)int_or_0(info_dict, "u"); } @@ -263,23 +259,21 @@ void ConvoInfoVolatile::set(const convo::one_to_one& c) { void ConvoInfoVolatile::set_base(const convo::base& c, DictFieldProxy& info) { auto r = info["r"]; - // If we're making the last_read value *older* for some reason then ignore the prune cutoff - // (because we might be intentionally resetting the value after a deletion, for instance). - if (auto* val = r.integer(); val && c.last_read < *val) - r = c.last_read; - else { - std::chrono::system_clock::time_point last_read{std::chrono::milliseconds{c.last_read}}; - if (last_read > std::chrono::system_clock::now() - PRUNE_LOW) - info["r"] = c.last_read; - } + if (auto* val = r.integer(); + // If we're making the last_read value *older* for some reason then ignore the prune cutoff + // (because we might be intentionally resetting the value after a deletion, for instance). + (val && c.last_read < sys_milliseconds{std::chrono::milliseconds{*val}}) // + || + // Otherwise set it if it's more recent than the prune cutoff + c.last_read > std::chrono::system_clock::now() - PRUNE_LOW) + + r = c.last_read.time_since_epoch().count(); set_flag(info["u"], c.unread); } void ConvoInfoVolatile::prune_stale(std::chrono::milliseconds prune) { - const int64_t cutoff = std::chrono::duration_cast( - (std::chrono::system_clock::now() - prune).time_since_epoch()) - .count(); + const auto cutoff = std::chrono::system_clock::now() - prune; std::vector stale; for (auto it = begin_1to1(); it != end(); ++it) diff --git a/src/config/groups/info.cpp b/src/config/groups/info.cpp index 2025ca0a..92cdc41c 100644 --- a/src/config/groups/info.cpp +++ b/src/config/groups/info.cpp @@ -3,13 +3,12 @@ #include #include -#include +#include #include "../internal.hpp" #include "session/config/error.h" #include "session/config/groups/info.h" #include "session/export.h" -#include "session/types.hpp" #include "session/util.hpp" using namespace std::literals; @@ -84,33 +83,47 @@ void Info::set_expiry_timer(std::chrono::seconds expiration_timer) { set_positive_int(data["E"], expiration_timer.count()); } +void Info::set_created(std::chrono::sys_seconds timestamp) { + set_ts(data["c"], timestamp); +} void Info::set_created(int64_t timestamp) { - set_positive_int(data["c"], to_epoch_seconds(timestamp)); + set_created(to_sys_seconds(timestamp)); } -std::optional Info::get_created() const { +std::optional Info::get_created() const { if (auto* ts = data["c"].integer()) - return to_epoch_seconds(*ts); + // Pass through to_sys_seconds because some past client bug may have stored ms here: + return to_sys_seconds(*ts); return std::nullopt; } +void Info::set_delete_before(std::chrono::sys_seconds timestamp) { + set_ts(data["d"], timestamp); +} + void Info::set_delete_before(int64_t timestamp) { - set_positive_int(data["d"], to_epoch_seconds(timestamp)); + set_delete_before(to_sys_seconds(timestamp)); } -std::optional Info::get_delete_before() const { +std::optional Info::get_delete_before() const { if (auto* ts = data["d"].integer()) - return to_epoch_seconds(*ts); + // Pass through to_sys_seconds because some past client bug may have stored ms here: + return to_sys_seconds(*ts); return std::nullopt; } +void Info::set_delete_attach_before(std::chrono::sys_seconds timestamp) { + set_ts(data["D"], timestamp); +} + void Info::set_delete_attach_before(int64_t timestamp) { - set_positive_int(data["D"], to_epoch_seconds(timestamp)); + set_delete_attach_before(to_sys_seconds(timestamp)); } -std::optional Info::get_delete_attach_before() const { +std::optional Info::get_delete_attach_before() const { if (auto* ts = data["D"].integer()) - return to_epoch_seconds(*ts); + // Pass through to_sys_seconds because some past client bug may have stored ms here: + return to_sys_seconds(*ts); return std::nullopt; } @@ -306,7 +319,11 @@ LIBSESSION_C_API void groups_info_set_expiry_timer(config_object* conf, int expi /// Outputs: /// - `int64_t` -- Unix timestamp when the group was created (if set by an admin). LIBSESSION_C_API int64_t groups_info_get_created(const config_object* conf) { - return unbox(conf)->get_created().value_or(0); + return unbox(conf) + ->get_created() + .value_or(std::chrono::sys_seconds{}) + .time_since_epoch() + .count(); } /// API: groups_info/groups_info_set_created @@ -318,7 +335,7 @@ LIBSESSION_C_API int64_t groups_info_get_created(const config_object* conf) { /// - `conf` -- [in] Pointer to the config object /// - `ts` -- [in] the unix timestamp, or 0 to clear a current value. LIBSESSION_C_API void groups_info_set_created(config_object* conf, int64_t ts) { - unbox(conf)->set_created(std::max(0, ts)); + unbox(conf)->set_created(to_sys_seconds(std::max(0, ts))); } /// API: groups_info/groups_info_get_delete_before @@ -332,7 +349,11 @@ LIBSESSION_C_API void groups_info_set_created(config_object* conf, int64_t ts) { /// Outputs: /// - `int64_t` -- Unix timestamp before which messages should be deleted. Returns 0 if not set. LIBSESSION_C_API int64_t groups_info_get_delete_before(const config_object* conf) { - return unbox(conf)->get_delete_before().value_or(0); + return unbox(conf) + ->get_delete_before() + .value_or(std::chrono::sys_seconds{}) + .time_since_epoch() + .count(); } /// API: groups_info/groups_info_set_delete_before @@ -344,7 +365,7 @@ LIBSESSION_C_API int64_t groups_info_get_delete_before(const config_object* conf /// - `conf` -- [in] Pointer to the config object /// - `ts` -- [in] the unix timestamp, or 0 to clear a current value. LIBSESSION_C_API void groups_info_set_delete_before(config_object* conf, int64_t ts) { - unbox(conf)->set_delete_before(std::max(0, ts)); + unbox(conf)->set_delete_before(to_sys_seconds(std::max(0, ts))); } /// API: groups_info/groups_info_get_attach_delete_before @@ -358,7 +379,11 @@ LIBSESSION_C_API void groups_info_set_delete_before(config_object* conf, int64_t /// Outputs: /// - `int64_t` -- Unix timestamp before which messages should be deleted. Returns 0 if not set. LIBSESSION_C_API int64_t groups_info_get_attach_delete_before(const config_object* conf) { - return unbox(conf)->get_delete_attach_before().value_or(0); + return unbox(conf) + ->get_delete_attach_before() + .value_or(std::chrono::sys_seconds{}) + .time_since_epoch() + .count(); } /// API: groups_info/groups_info_set_attach_delete_before @@ -370,7 +395,7 @@ LIBSESSION_C_API int64_t groups_info_get_attach_delete_before(const config_objec /// - `conf` -- [in] Pointer to the config object /// - `ts` -- [in] the unix timestamp, or 0 to clear a current value. LIBSESSION_C_API void groups_info_set_attach_delete_before(config_object* conf, int64_t ts) { - unbox(conf)->set_delete_attach_before(std::max(0, ts)); + unbox(conf)->set_delete_attach_before(to_sys_seconds(std::max(0, ts))); } /// API: groups_info/groups_info_is_destroyed(const config_object* conf); diff --git a/src/config/user_groups.cpp b/src/config/user_groups.cpp index 7c8bf724..3fa7f357 100644 --- a/src/config/user_groups.cpp +++ b/src/config/user_groups.cpp @@ -6,16 +6,12 @@ #include #include -#include #include -#include #include #include "internal.hpp" -#include "session/config/error.h" #include "session/config/user_groups.h" #include "session/export.h" -#include "session/types.hpp" #include "session/util.hpp" using namespace std::literals; @@ -34,18 +30,18 @@ namespace session::config { template static void base_into(const base_group_info& self, T& c) { c.priority = self.priority; - c.joined_at = to_epoch_seconds(self.joined_at); + c.joined_at = self.joined_at.time_since_epoch().count(); c.notifications = static_cast(self.notifications); - c.mute_until = to_epoch_seconds(self.mute_until); + c.mute_until = self.mute_until.time_since_epoch().count(); c.invited = self.invited; } template static void base_from(base_group_info& self, const T& c) { self.priority = c.priority; - self.joined_at = to_epoch_seconds(c.joined_at); + self.joined_at = to_sys_seconds(c.joined_at); self.notifications = static_cast(c.notifications); - self.mute_until = to_epoch_seconds(c.mute_until); + self.mute_until = to_sys_seconds(c.mute_until); self.invited = c.invited; } @@ -127,7 +123,9 @@ void legacy_group_info::into(ugroups_legacy_group_info& c) && { void base_group_info::load(const dict& info_dict) { priority = int_or_0(info_dict, "+"); - joined_at = to_epoch_seconds(std::max(0, int_or_0(info_dict, "j"))); + // This value could have been accidentally stored in ms by a previous version, so pass it + // through to_sys_seconds: + joined_at = to_sys_seconds(std::max(0, int_or_0(info_dict, "j"))); int notify = int_or_0(info_dict, "@"); if (notify >= 0 && notify <= 3) @@ -135,7 +133,9 @@ void base_group_info::load(const dict& info_dict) { else notifications = notify_mode::defaulted; - mute_until = to_epoch_seconds(int_or_0(info_dict, "!")); + // This value could have been accidentally stored in ms by a previous version, so pass it + // through to_sys_seconds: + mute_until = to_sys_seconds(int_or_0(info_dict, "!")); invited = int_or_0(info_dict, "i"); } @@ -406,9 +406,9 @@ void UserGroups::set(const community_info& c) { void UserGroups::set_base(const base_group_info& bg, DictFieldProxy& info) const { set_nonzero_int(info["+"], bg.priority); - set_positive_int(info["j"], to_epoch_seconds(bg.joined_at)); + set_ts(info["j"], bg.joined_at); set_positive_int(info["@"], static_cast(bg.notifications)); - set_positive_int(info["!"], to_epoch_seconds(bg.mute_until)); + set_ts(info["!"], bg.mute_until); set_flag(info["i"], bg.invited); // We don't set n here because it's subtly different in the three group types } diff --git a/tests/test_config_contacts.cpp b/tests/test_config_contacts.cpp index e30ac306..835ec486 100644 --- a/tests/test_config_contacts.cpp +++ b/tests/test_config_contacts.cpp @@ -55,9 +55,9 @@ TEST_CASE("Contacts", "[config][contacts]") { CHECK_FALSE(c.approved_me); CHECK_FALSE(c.blocked); CHECK_FALSE(c.profile_picture); - CHECK(c.created == 0); + CHECK(c.created.time_since_epoch() == 0s); CHECK(c.notifications == session::config::notify_mode::defaulted); - CHECK(c.mute_until == 0); + CHECK(c.mute_until.time_since_epoch() == 0s); CHECK_FALSE(contacts.needs_push()); CHECK_FALSE(contacts.needs_dump()); @@ -68,9 +68,9 @@ TEST_CASE("Contacts", "[config][contacts]") { c.profile_updated = std::chrono::sys_seconds{1s}; c.approved = true; c.approved_me = true; - c.created = created_ts * 1'000; + c.created = session::to_sys_seconds(created_ts * 1'000); // test setting ms c.notifications = session::config::notify_mode::all; - c.mute_until = (now + 1800) * 1'000'000; + c.mute_until = session::to_sys_seconds((now + 1800) * 1'000'000); // test setting us contacts.set(c); @@ -116,9 +116,9 @@ TEST_CASE("Contacts", "[config][contacts]") { CHECK(x->approved_me); CHECK_FALSE(x->profile_picture); CHECK_FALSE(x->blocked); - CHECK(x->created == created_ts); + CHECK(x->created.time_since_epoch() == created_ts * 1s); CHECK(x->notifications == session::config::notify_mode::all); - CHECK(x->mute_until == now + 1800); + CHECK(x->mute_until.time_since_epoch() == (now + 1800) * 1s); auto another_id = "051111111111111111111111111111111111111111111111111111111111111111"sv; auto c2 = contacts2.get_or_construct(another_id); diff --git a/tests/test_config_convo_info_volatile.cpp b/tests/test_config_convo_info_volatile.cpp index 502ea132..a1e26273 100644 --- a/tests/test_config_convo_info_volatile.cpp +++ b/tests/test_config_convo_info_volatile.cpp @@ -8,6 +8,7 @@ #include #include +#include "session/util.hpp" #include "utils.hpp" TEST_CASE("Conversations", "[config][conversations]") { @@ -48,15 +49,15 @@ TEST_CASE("Conversations", "[config][conversations]") { auto c = convos.get_or_construct_1to1(definitely_real_id); CHECK(c.session_id == definitely_real_id); - CHECK(c.last_read == 0); + CHECK(c.last_read.time_since_epoch() == 0s); CHECK_FALSE(convos.needs_push()); CHECK_FALSE(convos.needs_dump()); CHECK(std::get(convos.push()) == 0); - auto now_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); + auto now_ms = std::chrono::time_point_cast( + std::chrono::system_clock::now()); + static_assert(std::same_as); c.last_read = now_ms; @@ -88,7 +89,7 @@ TEST_CASE("Conversations", "[config][conversations]") { auto g = convos.get_or_construct_group(benders_nightmare_group); CHECK(g.id == benders_nightmare_group); - CHECK(g.last_read == 0); + CHECK(g.last_read.time_since_epoch() == 0s); CHECK_FALSE(g.unread); g.last_read = now_ms; @@ -100,7 +101,7 @@ TEST_CASE("Conversations", "[config][conversations]") { auto lb = convos.get_or_construct_blinded_1to1(legacy_blinded_id, true); CHECK(lb.blinded_session_id == legacy_blinded_id); - CHECK(lb.last_read == 0); + CHECK(lb.last_read.time_since_epoch() == 0s); CHECK_FALSE(lb.unread); lb.last_read = now_ms; @@ -109,7 +110,7 @@ TEST_CASE("Conversations", "[config][conversations]") { auto b = convos.get_or_construct_blinded_1to1(blinded_id, false); CHECK(b.blinded_session_id == blinded_id); - CHECK(b.last_read == 0); + CHECK(b.last_read.time_since_epoch() == 0s); CHECK_FALSE(b.unread); b.last_read = now_ms; @@ -174,7 +175,7 @@ TEST_CASE("Conversations", "[config][conversations]") { auto c3 = convos2.get_or_construct_legacy_group( "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"); - c3.last_read = now_ms - 50; + c3.last_read = now_ms - 50ms; convos2.set(c3); auto c4 = convos2.get_or_construct_blinded_1to1( @@ -605,29 +606,26 @@ TEST_CASE("Conversation pruning", "[config][conversations][pruning]") { auto pk = some_pubkey(x); return "05" + oxenc::to_hex(pk.begin(), pk.end()); }; - const auto now = std::chrono::system_clock::now() - 1ms; - auto unix_timestamp = [&now](int days_ago) -> int64_t { - return std::chrono::duration_cast( - (now - days_ago * 24h).time_since_epoch()) - .count(); - }; + const auto now = std::chrono::time_point_cast( + std::chrono::system_clock::now()) - + 1ms; for (int i = 0; i <= 65; i++) { if (i % 3 == 0) { auto c = convos.get_or_construct_1to1(some_session_id(i)); - c.last_read = unix_timestamp(i); + c.last_read = now - i * 24h; if (i % 5 == 0) c.unread = true; convos.set(c); } else if (i % 3 == 1) { auto c = convos.get_or_construct_legacy_group(some_session_id(i)); - c.last_read = unix_timestamp(i); + c.last_read = now - i * 24h; if (i % 5 == 0) c.unread = true; convos.set(c); } else { auto c = convos.get_or_construct_community( "https://example.org", "room{}"_format(i), some_pubkey(i)); - c.last_read = unix_timestamp(i); + c.last_read = now - i * 24h; if (i % 5 == 0) c.unread = true; convos.set(c); @@ -652,13 +650,19 @@ TEST_CASE("Conversation pruning", "[config][conversations][pruning]") { // internals like this!) // These ones wouldn't be stored by the normal `set()` interface, but won't get pruned either: - convos.data["1"][oxenc::from_hex(some_session_id(80))]["r"] = unix_timestamp(33); - convos.data["1"][oxenc::from_hex(some_session_id(81))]["r"] = unix_timestamp(40); - convos.data["1"][oxenc::from_hex(some_session_id(82))]["r"] = unix_timestamp(44); + convos.data["1"][oxenc::from_hex(some_session_id(80))]["r"] = + (now - 33 * 24h).time_since_epoch().count(); + convos.data["1"][oxenc::from_hex(some_session_id(81))]["r"] = + (now - 40 * 24h).time_since_epoch().count(); + convos.data["1"][oxenc::from_hex(some_session_id(82))]["r"] = + (now - 44 * 24h).time_since_epoch().count(); // These ones should get pruned as soon as we push: - convos.data["1"][oxenc::from_hex(some_session_id(83))]["r"] = unix_timestamp(45); - convos.data["1"][oxenc::from_hex(some_session_id(84))]["r"] = unix_timestamp(46); - convos.data["1"][oxenc::from_hex(some_session_id(85))]["r"] = unix_timestamp(1000); + convos.data["1"][oxenc::from_hex(some_session_id(83))]["r"] = + (now - 45 * 24h).time_since_epoch().count(); + convos.data["1"][oxenc::from_hex(some_session_id(84))]["r"] = + (now - 46 * 24h).time_since_epoch().count(); + convos.data["1"][oxenc::from_hex(some_session_id(85))]["r"] = + (now - 1000 * 24h).time_since_epoch().count(); CHECK(convos.size_1to1() == 19); int count = 0; diff --git a/tests/test_config_user_groups.cpp b/tests/test_config_user_groups.cpp index db94916b..be738a1f 100644 --- a/tests/test_config_user_groups.cpp +++ b/tests/test_config_user_groups.cpp @@ -4,12 +4,12 @@ #include #include -#include #include #include #include #include "session/config/notify.hpp" +#include "session/util.hpp" #include "utils.hpp" static constexpr int64_t created_ts = 1680064059; @@ -117,9 +117,9 @@ TEST_CASE("User Groups", "[config][groups]") { CHECK(c.priority == 0); CHECK(c.name == ""); CHECK(c.members().empty()); - CHECK(c.joined_at == 0); + CHECK(c.joined_at.time_since_epoch() == 0s); CHECK(c.notifications == session::config::notify_mode::defaulted); - CHECK(c.mute_until == 0); + CHECK(c.mute_until.time_since_epoch() == 0s); CHECK_FALSE(groups.needs_push()); CHECK_FALSE(groups.needs_dump()); @@ -136,9 +136,9 @@ TEST_CASE("User Groups", "[config][groups]") { c.name = "Englishmen"; c.disappearing_timer = 60min; - c.joined_at = created_ts * 1000; // milliseconds + c.joined_at = session::to_sys_seconds(created_ts * 1000); // milliseconds c.notifications = session::config::notify_mode::mentions_only; - c.mute_until = now + 3600; + c.mute_until = session::to_sys_seconds(now + 3600); CHECK(c.insert(users[0], false)); CHECK(c.insert(users[1], true)); CHECK(c.insert(users[2], false)); @@ -243,9 +243,9 @@ TEST_CASE("User Groups", "[config][groups]") { CHECK(c1.priority == 3); CHECK(c1.members() == expected_members); CHECK(c1.name == "Englishmen"); - CHECK(c1.joined_at == created_ts); + CHECK(c1.joined_at.time_since_epoch() == created_ts * 1s); CHECK(c1.notifications == session::config::notify_mode::mentions_only); - CHECK(c1.mute_until == now + 3600); + CHECK(c1.mute_until.time_since_epoch() == (now + 3600) * 1s); CHECK_FALSE(g2.needs_push()); CHECK_FALSE(g2.needs_dump()); @@ -457,9 +457,9 @@ TEST_CASE("User Groups -- (non-legacy) groups", "[config][groups][new]") { CHECK(c.secretkey.empty()); CHECK(c.id == definitely_real_id); CHECK(c.priority == 0); - CHECK(c.joined_at == 0); + CHECK(c.joined_at.time_since_epoch() == 0s); CHECK(c.notifications == session::config::notify_mode::defaulted); - CHECK(c.mute_until == 0); + CHECK(c.mute_until.time_since_epoch() == 0s); c.secretkey = session::to_vector(ed_sk); // This *isn't* the right secret key for the group, so // won't propagate, and so auth data will: @@ -485,16 +485,16 @@ TEST_CASE("User Groups -- (non-legacy) groups", "[config][groups][new]") { CHECK(c2->id == definitely_real_id); CHECK(c2->priority == 0); - CHECK(c2->joined_at == 0); + CHECK(c2->joined_at.time_since_epoch() == 0s); CHECK(c2->notifications == session::config::notify_mode::defaulted); - CHECK(c2->mute_until == 0); + CHECK(c2->mute_until.time_since_epoch() == 0s); CHECK_FALSE(c2->invited); CHECK(c2->name == ""); c2->priority = 123; - c2->joined_at = (int64_t)1'234'567'890 * 1'000; + c2->joined_at = session::to_sys_seconds((int64_t)1'234'567'890 * 1'000); // ms c2->notifications = session::config::notify_mode::mentions_only; - c2->mute_until = (int64_t)456'789'012 * 1'000'000; + c2->mute_until = session::to_sys_seconds((int64_t)456'789'012 * 1'000'000); // µs c2->invited = true; c2->name = "Magic Special Room"; @@ -526,9 +526,9 @@ TEST_CASE("User Groups -- (non-legacy) groups", "[config][groups][new]") { "0000000000000000000000000000"); CHECK(c3->id == definitely_real_id); CHECK(c3->priority == 123); - CHECK(c3->joined_at == 1234567890); + CHECK(c3->joined_at.time_since_epoch() == 1234567890s); CHECK(c3->notifications == session::config::notify_mode::mentions_only); - CHECK(c3->mute_until == 456789012); + CHECK(c3->mute_until.time_since_epoch() == 456789012s); CHECK(c3->invited); CHECK(c3->name == "Magic Special Room"); @@ -731,7 +731,7 @@ TEST_CASE("User Groups members C API", "[config][groups][c]") { auto grp = c2.get_legacy_group(definitely_real_id); REQUIRE(grp); CHECK(grp->members() == expected_members); - CHECK(grp->joined_at == created_ts); + CHECK(grp->joined_at.time_since_epoch() == created_ts * 1s); } TEST_CASE("User groups empty member bug", "[config][groups][bug]") { @@ -843,30 +843,35 @@ TEST_CASE("User groups mute_until & joined_at are always seconds", "[config][gro { auto lg = c.get_or_construct_legacy_group( "051234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); - int64_t joined_at = get_timestamp_us(); - int64_t mute_until = get_timestamp_s(); - lg.joined_at = joined_at; - lg.mute_until = mute_until; + int64_t joined_at_raw = get_timestamp_us(); + int64_t mute_until_raw = get_timestamp_s(); + auto joined_at = joined_at_raw * 1us; + auto mute_until = mute_until_raw * 1s; + lg.joined_at = session::to_sys_seconds(joined_at_raw); // µs + lg.mute_until = session::to_sys_seconds(mute_until_raw); // s c.set(lg); auto lg2 = c.get_or_construct_legacy_group( "051234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); - CHECK(lg2.joined_at == joined_at / 1'000'000); // joined_at was given in microseconds - CHECK(lg2.mute_until == mute_until); // mute_until was given in seconds + CHECK(lg2.joined_at.time_since_epoch() == joined_at - joined_at % 1s); + CHECK(lg2.mute_until.time_since_epoch() == mute_until); c.erase_legacy_group("051234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); } { auto gr = c.get_or_construct_group( "031234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); - int64_t joined_at = get_timestamp_ms(); - int64_t mute_until = get_timestamp_us(); - gr.joined_at = joined_at; - gr.mute_until = mute_until; + int64_t joined_at_raw = get_timestamp_ms(); + int64_t mute_until_raw = get_timestamp_us(); + auto joined_at = joined_at_raw * 1ms; + auto mute_until = mute_until_raw * 1us; + gr.joined_at = session::to_sys_seconds(joined_at_raw); // ms + gr.mute_until = session::to_sys_seconds(mute_until_raw); // µs c.set(gr); auto gr2 = c.get_or_construct_group( "031234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); - CHECK(gr2.joined_at == joined_at / 1'000); // joined_at was given in milliseconds - CHECK(gr2.mute_until == mute_until / 1'000'000); // mute_until was given in microseconds + // Non-whole second timestamp components should have been truncate: + CHECK(gr2.joined_at.time_since_epoch() == joined_at - joined_at % 1s); + CHECK(gr2.mute_until.time_since_epoch() == mute_until - mute_until % 1s); c.erase_group("031234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); } @@ -876,14 +881,16 @@ TEST_CASE("User groups mute_until & joined_at are always seconds", "[config][gro const auto url = "http://example.org:5678"; const auto room = "sudoku_room"; auto comm = c.get_or_construct_community(url, room, open_group_pubkey); - int64_t joined_at = get_timestamp_ms(); - int64_t mute_until = get_timestamp_ms(); - comm.joined_at = joined_at; - comm.mute_until = mute_until; + int64_t joined_at_raw = get_timestamp_ms(); + int64_t mute_until_raw = get_timestamp_ms(); + auto joined_at = joined_at_raw * 1ms; + auto mute_until = mute_until_raw * 1ms; + comm.joined_at = session::to_sys_seconds(joined_at_raw); + comm.mute_until = session::to_sys_seconds(mute_until_raw); c.set(comm); auto comm2 = c.get_or_construct_community(url, room, open_group_pubkey); - CHECK(comm2.joined_at == joined_at / 1'000); // joined_at was given in milliseconds - CHECK(comm2.mute_until == mute_until / 1'000); // mute_until was given in milliseconds + CHECK(comm2.joined_at.time_since_epoch() == joined_at - joined_at % 1s); // ms + CHECK(comm2.mute_until.time_since_epoch() == mute_until - mute_until % 1s); // ms c.erase_community(url, room); } { @@ -891,24 +898,19 @@ TEST_CASE("User groups mute_until & joined_at are always seconds", "[config][gro // - an invalid joined_at (1'733'979'503'520) and // - an invalid mute_until (1'733'979'503'520'780) values const auto dump_with_not_seconds = - "64313a21693165313a243231303a64313a23693165313a2664313a676433333a031234567890abcdef" - "1234" - "567890abcdef1234567890abcdef1234567890abcdef64313a21693137333339373935303335323037" - "3830" - "65313a4b303a313a6a693137333339373935303335323065656565313a3c6c6c69306533323aea173b" - "57be" - "ca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c96564656565313a3d64313a67643333" - "3a03" - "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef64313a21303a313a4b" - "303a" - "313a6a303a65656565313a28303a313a296c6565"_hexbytes; + "64313a21693165313a243231303a64313a23693165313a2664313a676433333a031234567890abcd" + "ef1234567890abcdef1234567890abcdef1234567890abcdef64313a216931373333393739353033" + "35323037383065313a4b303a313a6a693137333339373935303335323065656565313a3c6c6c6930" + "6533323aea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c96564656565" + "313a3d64313a676433333a031234567890abcdef1234567890abcdef1234567890abcdef12345678" + "90abcdef64313a21303a313a4b303a313a6a303a65656565313a28303a313a296c6565"_hexbytes; session::config::UserGroups c2{std::span{seed}, dump_with_not_seconds}; auto gr = c2.get_or_construct_group( "031234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); - CHECK(gr.joined_at == 1'733'979'503'520 / 1'000); - CHECK(gr.mute_until == 1'733'979'503'520'780 / 1'000'000); + CHECK(gr.joined_at.time_since_epoch() == 1'733'979'503'520ms - 520ms); + CHECK(gr.mute_until.time_since_epoch() == 1'733'979'503'520'780us - 520'780us); } } diff --git a/tests/test_group_info.cpp b/tests/test_group_info.cpp index 4dfea5c1..638786c7 100644 --- a/tests/test_group_info.cpp +++ b/tests/test_group_info.cpp @@ -76,9 +76,11 @@ TEST_CASE("Group Info settings", "[config][groups][info]") { "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"_hexbytes); ginfo2.set_expiry_timer(1h); constexpr int64_t create_time{1682529839}; - ginfo2.set_created(create_time); - ginfo2.set_delete_before((create_time + 50 * 86400) * 1'000'000); // as microseconds - ginfo2.set_delete_attach_before((create_time + 70 * 86400) * 1'000); // as milliseconds + ginfo2.set_created(session::to_sys_seconds(create_time)); + // µs: + ginfo2.set_delete_before(session::to_sys_seconds((create_time + 50 * 86400) * 1'000'000)); + // ms: + ginfo2.set_delete_attach_before(session::to_sys_seconds((create_time + 70 * 86400) * 1'000)); ginfo2.destroy_group(); auto [s2, p2, o2] = ginfo2.push(); @@ -106,14 +108,18 @@ TEST_CASE("Group Info settings", "[config][groups][info]") { CHECK(ginfo1.needs_push()); auto [s3, p3, o3] = ginfo1.push(); + constexpr std::chrono::sys_seconds expected_created{create_time * 1s}; + constexpr std::chrono::sys_seconds expected_del_before{create_time * 1s + 50 * 24h}; + constexpr std::chrono::sys_seconds expected_del_attach{create_time * 1s + 70 * 24h}; + CHECK(ginfo1.get_name() == "Better name!"); CHECK(ginfo1.get_profile_pic().url == "http://example.com/12345"); CHECK(ginfo1.get_profile_pic().key == "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"_hexbytes); CHECK(ginfo1.get_expiry_timer() == 1h); - CHECK(ginfo1.get_created() == create_time); - CHECK(ginfo1.get_delete_before() == create_time + 50 * 86400); - CHECK(ginfo1.get_delete_attach_before() == create_time + 70 * 86400); + CHECK(ginfo1.get_created() == expected_created); + CHECK(ginfo1.get_delete_before() == expected_del_before); + CHECK(ginfo1.get_delete_attach_before() == expected_del_attach); CHECK(ginfo1.is_destroyed()); ginfo1.confirm_pushed(s3, {"fakehash3"}); @@ -126,9 +132,9 @@ TEST_CASE("Group Info settings", "[config][groups][info]") { CHECK(ginfo2.get_profile_pic().key == "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"_hexbytes); CHECK(ginfo2.get_expiry_timer() == 1h); - CHECK(ginfo2.get_created() == create_time); - CHECK(ginfo2.get_delete_before() == create_time + 50 * 86400); - CHECK(ginfo2.get_delete_attach_before() == create_time + 70 * 86400); + CHECK(ginfo2.get_created() == expected_created); + CHECK(ginfo2.get_delete_before() == expected_del_before); + CHECK(ginfo2.get_delete_attach_before() == expected_del_attach); CHECK(ginfo2.is_destroyed()); CHECK_THROWS( @@ -245,7 +251,7 @@ TEST_CASE("Verify-only Group Info", "[config][groups][verify-only]") { // Now let's get more complicated: we will have *two* valid signers who submit competing updates ginfo_rw2.set_name("Super Group 2"); - ginfo_rw2.set_created(12345); + ginfo_rw2.set_created(session::to_sys_seconds(12345)); ginfo_rw.set_name("Super Group 3"); ginfo_rw.set_expiry_timer(365 * 24h); @@ -299,7 +305,7 @@ TEST_CASE("Verify-only Group Info", "[config][groups][verify-only]") { CHECK(*n == "Super Group 2"); auto c = g.get_created(); REQUIRE(c); - CHECK(*c == 12345); + CHECK(c->time_since_epoch() == 12345s); auto et = g.get_expiry_timer(); REQUIRE(et); CHECK(*et == 365 * 24h);