Skip to content

Commit 547bcab

Browse files
committed
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<std::chrono::milliseconds> (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.
1 parent 0bcc986 commit 547bcab

16 files changed

+243
-189
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.1
20+
VERSION 1.6.0
2121
DESCRIPTION "Session client utility library"
2222
LANGUAGES ${LANGS})
2323

include/session/config/contacts.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ typedef struct contacts_contact {
2828

2929
int priority;
3030
CONVO_NOTIFY_MODE notifications;
31-
int64_t mute_until;
31+
int64_t mute_until; // unix timestamp (seconds)
3232

3333
CONVO_EXPIRATION_MODE exp_mode;
3434
int exp_seconds;

include/session/config/contacts.hpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,13 @@ struct contact_info {
8282
// conversation is hidden. Otherwise (0) this is a regular, unpinned
8383
// conversation.
8484
notify_mode notifications = notify_mode::defaulted;
85-
int64_t mute_until = 0; // If non-zero, disable notifications until the given unix timestamp
86-
// (seconds, overriding whatever the current `notifications` value is
87-
// until the timestamp expires).
85+
std::chrono::sys_seconds mute_until{0s}; // If timestamp is non-zero, disable notifications
86+
// until the given unix timestamp (seconds, overriding
87+
// whatever the current `notifications` value is until
88+
// the timestamp expires).
8889
expiration_mode exp_mode = expiration_mode::none; // The expiry time; none if not expiring.
8990
std::chrono::seconds exp_timer{0}; // The expiration timer (in seconds)
90-
int64_t created = 0; // Unix timestamp (seconds) when this contact was added
91+
std::chrono::sys_seconds created{0s}; // Unix timestamp (seconds) when this contact was added
9192

9293
explicit contact_info(std::string sid);
9394

@@ -384,7 +385,14 @@ class Contacts : public ConfigBase {
384385
/// Inputs:
385386
/// - `session_id` -- hex string of the session id
386387
/// - `timestamp` -- standard unix timestamp of the time contact was created
387-
void set_created(std::string_view session_id, int64_t timestamp);
388+
void set_created(std::string_view session_id, std::chrono::sys_seconds timestamp);
389+
390+
/// Deprecated: takes timestamp as an integer and guess whether it is seconds, milliseconds, or
391+
/// microseconds.
392+
[[deprecated(
393+
"pass a std::chrono::sys_seconds instead (perhaps using "
394+
"session::to_sys_seconds)")]] void
395+
set_created(std::string_view session_id, int64_t timestamp);
388396

389397
/// API: contacts/contacts::erase
390398
///

include/session/config/convo_info_volatile.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class val_loader;
6767
namespace convo {
6868

6969
struct base {
70-
int64_t last_read = 0;
70+
sys_milliseconds last_read{};
7171
bool unread = false;
7272

7373
protected:

include/session/config/groups/info.hpp

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#pragma once
22

33
#include <chrono>
4-
#include <memory>
54
#include <session/config.hpp>
65

76
#include "../base.hpp"
@@ -220,7 +219,11 @@ class Info : public ConfigBase {
220219
/// Inputs:
221220
/// - `session_id` -- hex string of the session id
222221
/// - `timestamp` -- standard unix timestamp when the group was created
223-
void set_created(int64_t timestamp);
222+
void set_created(std::chrono::sys_seconds timestamp);
223+
/// Deprecated version that attempts to guess whether the input is seconds, milliseconds, or
224+
/// microseconds.
225+
[[deprecated("pass a std::chrono::sys_seconds instead (or use session::to_sys_seconds)")]] void
226+
set_created(int64_t timestamp);
224227

225228
/// API: groups/Info::get_created
226229
///
@@ -229,23 +232,24 @@ class Info : public ConfigBase {
229232
/// Inputs: none.
230233
///
231234
/// Outputs:
232-
/// - `std::optional<int64_t>` -- the unix timestamp when the group was created, or nullopt if
233-
/// the creation timestamp is not set.
234-
std::optional<int64_t> get_created() const;
235+
/// - `std::chrono::sys_seconds` -- the unix timestamp when the group was
236+
/// created, or nullopt if the creation timestamp is not set.
237+
std::optional<std::chrono::sys_seconds> get_created() const;
235238

236239
/// API: groups/Info::set_delete_before
237240
///
238241
/// Sets a "delete before" unix timestamp: this instructs clients to delete all messages from
239242
/// the closed group history with a timestamp earlier than this value. Returns nullopt if no
240243
/// delete-before timestamp is set.
241244
///
242-
/// The given value is checked for sanity (e.g. if you pass milliseconds it will be
243-
/// interpreted as such)
244-
///
245245
/// Inputs:
246246
/// - `timestamp` -- the new unix timestamp before which clients should delete messages. Pass 0
247247
/// (or negative) to disable the delete-before timestamp.
248-
void set_delete_before(int64_t timestamp);
248+
void set_delete_before(std::chrono::sys_seconds timestamp);
249+
/// Deprecated version that attempts to guess whether you meant seconds, milliseconds, or
250+
/// microseconds.
251+
[[deprecated("pass a std::chrono::sys_seconds instead (or use session::to_sys_seconds)")]] void
252+
set_delete_before(int64_t timestamp);
249253

250254
/// API: groups/Info::get_delete_before
251255
///
@@ -257,8 +261,9 @@ class Info : public ConfigBase {
257261
/// Inputs: none.
258262
///
259263
/// Outputs:
260-
/// - `int64_t` -- the unix timestamp for which all older messages shall be delete
261-
std::optional<int64_t> get_delete_before() const;
264+
/// - `sys_seconds` -- the unix timestamp for which all older messages shall be deleted, or
265+
/// nullopt if there is no delete-before timestamp set.
266+
std::optional<std::chrono::sys_seconds> get_delete_before() const;
262267

263268
/// API: groups/Info::set_delete_attach_before
264269
///
@@ -272,9 +277,12 @@ class Info : public ConfigBase {
272277
///
273278
/// Inputs:
274279
/// - `timestamp` -- the new unix timestamp before which clients should delete attachments. Pass
275-
/// 0
276-
/// (or negative) to disable the delete-attachment-before timestamp.
277-
void set_delete_attach_before(int64_t timestamp);
280+
/// 0 (or negative) to disable the delete-attachment-before timestamp.
281+
void set_delete_attach_before(std::chrono::sys_seconds timestamp);
282+
/// Deprecated version that attempts to guess whether you meant seconds, milliseconds, or
283+
/// microseconds.
284+
[[deprecated("pass a std::chrono::sys_seconds instead (or use session::to_sys_seconds)")]] void
285+
set_delete_attach_before(int64_t timestamp);
278286

279287
/// API: groups/Info::get_delete_attach_before
280288
///
@@ -286,8 +294,9 @@ class Info : public ConfigBase {
286294
/// Inputs: none.
287295
///
288296
/// Outputs:
289-
/// - `int64_t` -- the unix timestamp for which all older message attachments shall be deleted
290-
std::optional<int64_t> get_delete_attach_before() const;
297+
/// - `sys_seconds` -- the unix timestamp for which all older message attachments shall be
298+
/// deleted, or nullopt if delete-attach-before is not enabled.
299+
std::optional<std::chrono::sys_seconds> get_delete_attach_before() const;
291300

292301
/// API: groups/Info::destroy_group
293302
///

include/session/config/user_groups.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ extern "C" {
66

77
#include "base.h"
88
#include "notify.h"
9-
#include "util.h"
109

1110
// Maximum length of a group name, in bytes
1211
LIBSESSION_EXPORT extern const size_t GROUP_NAME_MAX_LENGTH;

include/session/config/user_groups.hpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,13 @@ namespace session::config {
8282
struct base_group_info {
8383
static constexpr size_t NAME_MAX_LENGTH = 100; // in bytes; name will be truncated if exceeded
8484

85-
int priority = 0; // The priority; 0 means unpinned, -1 means hidden, positive means
86-
// pinned higher (i.e. higher priority conversations come first).
87-
int64_t joined_at = 0; // unix timestamp (seconds) when the group was joined (or re-joined)
85+
int priority = 0; // The priority; 0 means unpinned, -1 means hidden, positive means
86+
// pinned higher (i.e. higher priority conversations come first).
87+
std::chrono::sys_seconds joined_at{}; // unix timestamp (seconds) when the group
88+
// was joined (or re-joined)
8889
notify_mode notifications = notify_mode::defaulted; // When the user wants notifications
89-
int64_t mute_until = 0; // unix timestamp (seconds) until which notifications are disabled
90+
std::chrono::sys_seconds mute_until{}; // unix timestamp (seconds) until which
91+
// notifications are disabled
9092

9193
std::string name; // human-readable; always set for a legacy closed group, only used before
9294
// joining a new closed group (after joining the group info provide the name)

include/session/util.hpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,7 @@ inline std::string utf8_truncate(std::string val, size_t n) {
249249
return val;
250250
}
251251

252-
// Helper function to transform a timestamp provided in seconds, milliseconds or microseconds to
253-
// seconds
254-
inline int64_t to_epoch_seconds(int64_t timestamp) {
255-
return timestamp > 9'000'000'000'000 ? timestamp / 1'000'000
256-
: timestamp > 9'000'000'000 ? timestamp / 1'000
257-
: timestamp;
258-
}
252+
using sys_milliseconds = std::chrono::sys_time<std::chrono::milliseconds>;
259253

260254
// Takes a timestamp as unix epoch seconds (not ms, µs) and wraps it in a sys_seconds containing it.
261255
inline std::chrono::sys_seconds as_sys_seconds(int64_t timestamp) {
@@ -276,4 +270,10 @@ static_assert(std::is_same_v<
276270
std::chrono::seconds,
277271
decltype(std::declval<std::chrono::sys_seconds>().time_since_epoch())>);
278272

273+
// Takes a timestamp as unix epoch milliseconds (not seconds, or microseconds) and wraps it in a
274+
// sys_ms containing it.
275+
inline sys_milliseconds as_sys_ms(int64_t timestamp) {
276+
return sys_milliseconds{std::chrono::milliseconds{timestamp}};
277+
}
278+
279279
} // namespace session

src/config/contacts.cpp

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <oxenc/hex.h>
66
#include <sodium/crypto_generichash_blake2b.h>
77

8+
#include <chrono>
89
#include <oxen/log.hpp>
910
#include <oxen/log/format.hpp>
1011
#include <variant>
@@ -92,7 +93,9 @@ void contact_info::load(const dict& info_dict) {
9293
} else {
9394
notifications = notify_mode::defaulted;
9495
}
95-
mute_until = to_epoch_seconds(int_or_0(info_dict, "!"));
96+
// Older client versions might have accidentally stored this as ms, so run it through
97+
// to_sys_seconds:
98+
mute_until = to_sys_seconds(int_or_0(info_dict, "!"));
9699

97100
int exp_mode_ = int_or_0(info_dict, "e");
98101
if (exp_mode_ >= static_cast<int>(expiration_mode::none) &&
@@ -113,7 +116,9 @@ void contact_info::load(const dict& info_dict) {
113116
}
114117
}
115118

116-
created = to_epoch_seconds(int_or_0(info_dict, "j"));
119+
// Older client versions might have accidentally stored this as ms, so run it through
120+
// to_sys_seconds:
121+
created = to_sys_seconds(int_or_0(info_dict, "j"));
117122
}
118123

119124
void contact_info::into(contacts_contact& c) const {
@@ -132,12 +137,12 @@ void contact_info::into(contacts_contact& c) const {
132137
c.blocked = blocked;
133138
c.priority = priority;
134139
c.notifications = static_cast<CONVO_NOTIFY_MODE>(notifications);
135-
c.mute_until = to_epoch_seconds(mute_until);
140+
c.mute_until = mute_until.time_since_epoch().count();
136141
c.exp_mode = static_cast<CONVO_EXPIRATION_MODE>(exp_mode);
137142
c.exp_seconds = exp_timer.count();
138143
if (c.exp_seconds <= 0 && c.exp_mode != CONVO_EXPIRATION_NONE)
139144
c.exp_mode = CONVO_EXPIRATION_NONE;
140-
c.created = to_epoch_seconds(created);
145+
c.created = created.time_since_epoch().count();
141146
}
142147

143148
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,
156161
blocked = c.blocked;
157162
priority = c.priority;
158163
notifications = static_cast<notify_mode>(c.notifications);
159-
mute_until = to_epoch_seconds(c.mute_until);
164+
mute_until = to_sys_seconds(c.mute_until);
160165
exp_mode = static_cast<expiration_mode>(c.exp_mode);
161166
exp_timer = exp_mode == expiration_mode::none ? 0s : std::chrono::seconds{c.exp_seconds};
162167
if (exp_timer <= 0s && exp_mode != expiration_mode::none)
163168
exp_mode = expiration_mode::none;
164-
created = to_epoch_seconds(c.created);
169+
created = to_sys_seconds(c.created);
165170
}
166171

167172
std::optional<contact_info> Contacts::get(std::string_view pubkey_hex) const {
@@ -211,7 +216,7 @@ void Contacts::set(const contact_info& contact) {
211216
if (notify == notify_mode::mentions_only)
212217
notify = notify_mode::all;
213218
set_positive_int(info["@"], static_cast<int>(notify));
214-
set_positive_int(info["!"], to_epoch_seconds(contact.mute_until));
219+
set_ts(info["!"], contact.mute_until);
215220

216221
set_pair_if(
217222
contact.exp_mode != expiration_mode::none && contact.exp_timer > 0s,
@@ -220,7 +225,7 @@ void Contacts::set(const contact_info& contact) {
220225
info["E"],
221226
contact.exp_timer.count());
222227

223-
set_positive_int(info["j"], to_epoch_seconds(contact.created));
228+
set_ts(info["j"], contact.created);
224229
}
225230

226231
void Contacts::set_name(std::string_view session_id, std::string name) {
@@ -285,9 +290,9 @@ void Contacts::set_expiry(
285290
set(c);
286291
}
287292

288-
void Contacts::set_created(std::string_view session_id, int64_t timestamp) {
293+
void Contacts::set_created(std::string_view session_id, std::chrono::sys_seconds timestamp) {
289294
auto c = get_or_construct(session_id);
290-
c.created = to_epoch_seconds(timestamp);
295+
c.created = timestamp;
291296
set(c);
292297
}
293298

0 commit comments

Comments
 (0)