Skip to content

Commit b6b5bed

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.
1 parent 76d2aef commit b6b5bed

File tree

13 files changed

+187
-139
lines changed

13 files changed

+187
-139
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.0
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
@@ -64,12 +64,13 @@ struct contact_info {
6464
// conversation is hidden. Otherwise (0) this is a regular, unpinned
6565
// conversation.
6666
notify_mode notifications = notify_mode::defaulted;
67-
int64_t mute_until = 0; // If non-zero, disable notifications until the given unix timestamp
68-
// (seconds, overriding whatever the current `notifications` value is
69-
// until the timestamp expires).
67+
std::chrono::sys_seconds mute_until{0s}; // If timestamp is non-zero, disable notifications
68+
// until the given unix timestamp (seconds, overriding
69+
// whatever the current `notifications` value is until
70+
// the timestamp expires).
7071
expiration_mode exp_mode = expiration_mode::none; // The expiry time; none if not expiring.
7172
std::chrono::seconds exp_timer{0}; // The expiration timer (in seconds)
72-
int64_t created = 0; // Unix timestamp (seconds) when this contact was added
73+
std::chrono::sys_seconds created{0s}; // Unix timestamp (seconds) when this contact was added
7374

7475
explicit contact_info(std::string sid);
7576

@@ -317,7 +318,14 @@ class Contacts : public ConfigBase {
317318
/// Inputs:
318319
/// - `session_id` -- hex string of the session id
319320
/// - `timestamp` -- standard unix timestamp of the time contact was created
320-
void set_created(std::string_view session_id, int64_t timestamp);
321+
void set_created(std::string_view session_id, std::chrono::sys_seconds timestamp);
322+
323+
/// Deprecated: takes timestamp as an integer and guess whether it is seconds, milliseconds, or
324+
/// microseconds.
325+
[[deprecated(
326+
"pass a std::chrono::sys_seconds instead (perhaps using "
327+
"session::to_sys_seconds)")]] void
328+
set_created(std::string_view session_id, int64_t timestamp);
321329

322330
/// API: contacts/contacts::erase
323331
///

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: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -249,14 +249,6 @@ 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-
}
259-
260252
// Takes a timestamp as unix epoch seconds (not ms, µs) and wraps it in a sys_seconds containing it.
261253
inline std::chrono::sys_seconds as_sys_seconds(int64_t timestamp) {
262254
return std::chrono::sys_seconds{std::chrono::seconds{timestamp}};

src/config/contacts.cpp

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

6+
#include <chrono>
67
#include <variant>
78

89
#include "internal.hpp"
@@ -98,7 +99,9 @@ void contact_info::load(const dict& info_dict) {
9899
} else {
99100
notifications = notify_mode::defaulted;
100101
}
101-
mute_until = to_epoch_seconds(int_or_0(info_dict, "!"));
102+
// Older client versions might have accidentally stored this as ms, so run it through
103+
// to_sys_seconds:
104+
mute_until = to_sys_seconds(int_or_0(info_dict, "!"));
102105

103106
int exp_mode_ = int_or_0(info_dict, "e");
104107
if (exp_mode_ >= static_cast<int>(expiration_mode::none) &&
@@ -119,7 +122,9 @@ void contact_info::load(const dict& info_dict) {
119122
}
120123
}
121124

122-
created = to_epoch_seconds(int_or_0(info_dict, "j"));
125+
// Older client versions might have accidentally stored this as ms, so run it through
126+
// to_sys_seconds:
127+
created = to_sys_seconds(int_or_0(info_dict, "j"));
123128
}
124129

125130
void contact_info::into(contacts_contact& c) const {
@@ -138,12 +143,12 @@ void contact_info::into(contacts_contact& c) const {
138143
c.blocked = blocked;
139144
c.priority = priority;
140145
c.notifications = static_cast<CONVO_NOTIFY_MODE>(notifications);
141-
c.mute_until = to_epoch_seconds(mute_until);
146+
c.mute_until = mute_until.time_since_epoch().count();
142147
c.exp_mode = static_cast<CONVO_EXPIRATION_MODE>(exp_mode);
143148
c.exp_seconds = exp_timer.count();
144149
if (c.exp_seconds <= 0 && c.exp_mode != CONVO_EXPIRATION_NONE)
145150
c.exp_mode = CONVO_EXPIRATION_NONE;
146-
c.created = to_epoch_seconds(created);
151+
c.created = created.time_since_epoch().count();
147152
}
148153

149154
contact_info::contact_info(const contacts_contact& c) : session_id{c.session_id, 66} {
@@ -162,12 +167,12 @@ contact_info::contact_info(const contacts_contact& c) : session_id{c.session_id,
162167
blocked = c.blocked;
163168
priority = c.priority;
164169
notifications = static_cast<notify_mode>(c.notifications);
165-
mute_until = to_epoch_seconds(c.mute_until);
170+
mute_until = to_sys_seconds(c.mute_until);
166171
exp_mode = static_cast<expiration_mode>(c.exp_mode);
167172
exp_timer = exp_mode == expiration_mode::none ? 0s : std::chrono::seconds{c.exp_seconds};
168173
if (exp_timer <= 0s && exp_mode != expiration_mode::none)
169174
exp_mode = expiration_mode::none;
170-
created = to_epoch_seconds(c.created);
175+
created = to_sys_seconds(c.created);
171176
}
172177

173178
std::optional<contact_info> Contacts::get(std::string_view pubkey_hex) const {
@@ -242,7 +247,7 @@ void Contacts::set(const contact_info& contact) {
242247
if (notify == notify_mode::mentions_only)
243248
notify = notify_mode::all;
244249
set_positive_int(info["@"], static_cast<int>(notify));
245-
set_positive_int(info["!"], to_epoch_seconds(contact.mute_until));
250+
set_ts(info["!"], contact.mute_until);
246251

247252
set_pair_if(
248253
contact.exp_mode != expiration_mode::none && contact.exp_timer > 0s,
@@ -251,7 +256,7 @@ void Contacts::set(const contact_info& contact) {
251256
info["E"],
252257
contact.exp_timer.count());
253258

254-
set_positive_int(info["j"], to_epoch_seconds(contact.created));
259+
set_ts(info["j"], contact.created);
255260
}
256261

257262
LIBSESSION_C_API bool contacts_set(config_object* conf, const contacts_contact* contact) {
@@ -326,9 +331,9 @@ void Contacts::set_expiry(
326331
set(c);
327332
}
328333

329-
void Contacts::set_created(std::string_view session_id, int64_t timestamp) {
334+
void Contacts::set_created(std::string_view session_id, std::chrono::sys_seconds timestamp) {
330335
auto c = get_or_construct(session_id);
331-
c.created = to_epoch_seconds(timestamp);
336+
c.created = timestamp;
332337
set(c);
333338
}
334339

0 commit comments

Comments
 (0)