Skip to content

Commit f12b91b

Browse files
authored
Merge pull request #3 from Bilb/feature/local_members_sending_state
use single letter for keys dump() & set pending_ send_state
2 parents f11f1ba + f10026f commit f12b91b

File tree

6 files changed

+109
-17
lines changed

6 files changed

+109
-17
lines changed

include/session/config/groups/keys.hpp

+14-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ using namespace std::literals;
3030
/// - it isn't compressed (since most of the data fields are encrypted or random, compression
3131
/// reduction would be minimal).
3232
///
33-
/// Fields used (in ascii order):
33+
/// Fields used for generating key messages (in ascii order):
3434
/// # -- 24-byte nonce used for all the encrypted values in this message; required.
3535
///
3636
/// For non-supplemental messages:
@@ -58,6 +58,19 @@ using namespace std::literals;
5858
/// `~` since it is the largest 7-bit ascii character value). Note that this signature
5959
/// mechanism works exactly the same as the signature on regular config messages.
6060
///
61+
/// Fields used for dumping the config (in ascii order):
62+
/// A -- active config messages list. A list of lists, where each list has:
63+
/// - as first argument the generation number
64+
/// - the rest are the hashes valid for that generation number
65+
/// L -- a list of dict representing all the keys. Each dict has:
66+
/// - k -- same as "For supplemental messages"
67+
/// - g -- same as "For supplemental messages"
68+
/// - t -- same as "For supplemental messages"
69+
/// P -- pending key as a dict (if present).
70+
/// - c -- the pending config message to push
71+
/// - g -- same as "For supplemental messages"
72+
/// - k -- same as "For supplemental messages"
73+
///
6174
/// Some extra details:
6275
///
6376
/// - each copy of the encryption key uses xchacha20_poly1305 using the `#` nonce

include/session/config/groups/members.hpp

+24-4
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,28 @@ class Members : public ConfigBase {
330330
/// - `member` - Returns a filled out member struct
331331
member get_or_construct(std::string_view pubkey_hex) const;
332332

333+
/// API: groups/Members::has_pending_send
334+
///
335+
/// This function can be used to check if a member is pending send locally.
336+
///
337+
/// Inputs:
338+
/// - `pubkey_hex` -- hex string of the session id
339+
///
340+
/// Outputs:
341+
/// - `bool` - true if that sessionid is marked as pending send locally
342+
bool has_pending_send(std::string pubkey_hex) const;
343+
344+
/// API: groups/Members::set_pending_send
345+
///
346+
/// This function can be used to set the pending send state of a member.
347+
/// If that effectively made a change, it will set _needs_dump to true.
348+
///
349+
/// Inputs:
350+
/// - `pubkey_hex` -- hex string of the session id
351+
/// - `pending` -- pending send state to set for that member
352+
///
353+
void set_pending_send(std::string pubkey_hex, bool pending);
354+
333355
/// API: groups/Members::get_status
334356
///
335357
/// This function goes through the various status values and returns a single consolidated
@@ -351,8 +373,7 @@ class Members : public ConfigBase {
351373

352374
// If the member is promoted then we return the relevant promoted status
353375
if (member.admin) {
354-
if (member.promotion_status == STATUS_NOT_SENT &&
355-
pending_send_ids.find(member.session_id) != pending_send_ids.end())
376+
if (member.promotion_status == STATUS_NOT_SENT && has_pending_send(member.session_id))
356377
return member::Status::promotion_sending;
357378
else if (member.promotion_status == STATUS_NOT_SENT)
358379
return member::Status::promotion_not_sent;
@@ -367,8 +388,7 @@ class Members : public ConfigBase {
367388
}
368389

369390
// Otherwise the member is a standard member
370-
if (member.invite_status == STATUS_NOT_SENT &&
371-
pending_send_ids.find(member.session_id) != pending_send_ids.end())
391+
if (member.invite_status == STATUS_NOT_SENT && has_pending_send(member.session_id))
372392
return member::Status::invite_sending;
373393
else if (member.invite_status == STATUS_NOT_SENT)
374394
return member::Status::invite_not_sent;

src/config/groups/keys.cpp

+6-6
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ ustring Keys::dump() {
7676
ustring Keys::make_dump() const {
7777
oxenc::bt_dict_producer d;
7878
{
79-
auto active = d.append_list("active");
79+
auto active = d.append_list("A");
8080
for (const auto& [gen, hashes] : active_msgs_) {
8181
auto lst = active.append_list();
8282
lst.append(gen);
@@ -86,7 +86,7 @@ ustring Keys::make_dump() const {
8686
}
8787

8888
{
89-
auto keys = d.append_list("keys");
89+
auto keys = d.append_list("L");
9090
for (auto& k : keys_) {
9191
auto ki = keys.append_dict();
9292
// NB: Keys must be in sorted order
@@ -101,7 +101,7 @@ ustring Keys::make_dump() const {
101101
}
102102

103103
if (!pending_key_config_.empty()) {
104-
auto pending = d.append_dict("pending");
104+
auto pending = d.append_dict("P");
105105
// NB: Keys must be in sorted order
106106
pending.append("c", from_unsigned_sv(pending_key_config_));
107107
pending.append("g", pending_gen_);
@@ -114,7 +114,7 @@ ustring Keys::make_dump() const {
114114
void Keys::load_dump(ustring_view dump) {
115115
oxenc::bt_dict_consumer d{from_unsigned_sv(dump)};
116116

117-
if (d.skip_until("active")) {
117+
if (d.skip_until("A")) {
118118
auto active = d.consume_list_consumer();
119119
while (!active.is_finished()) {
120120
auto lst = active.consume_list_consumer();
@@ -126,7 +126,7 @@ void Keys::load_dump(ustring_view dump) {
126126
throw config_value_error{"Invalid Keys dump: `active` not found"};
127127
}
128128

129-
if (d.skip_until("keys")) {
129+
if (d.skip_until("L")) {
130130
auto keys = d.consume_list_consumer();
131131
while (!keys.is_finished()) {
132132
auto kd = keys.consume_dict_consumer();
@@ -156,7 +156,7 @@ void Keys::load_dump(ustring_view dump) {
156156
throw config_value_error{"Invalid Keys dump: `keys` not found"};
157157
}
158158

159-
if (d.skip_until("pending")) {
159+
if (d.skip_until("P")) {
160160
auto pending = d.consume_dict_consumer();
161161

162162
if (!pending.skip_until("c"))

src/config/groups/members.cpp

+17-4
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,11 @@ void Members::set(const member& mem) {
7676
// add them to the `pending_send_ids` until they are given a new status
7777
if ((!mem.admin && mem.invite_status == STATUS_NOT_SENT) ||
7878
(mem.admin && mem.promotion_status == STATUS_NOT_SENT))
79-
pending_send_ids.emplace(mem.session_id);
79+
set_pending_send(mem.session_id, true);
8080
else if (
8181
(!mem.admin && mem.invite_status != STATUS_NOT_SENT) ||
8282
(mem.admin && mem.promotion_status != STATUS_NOT_SENT))
83-
pending_send_ids.erase(mem.session_id);
83+
set_pending_send(mem.session_id, false);
8484
}
8585

8686
void member::load(const dict& info_dict) {
@@ -150,8 +150,7 @@ bool Members::erase(std::string_view session_id) {
150150
bool ret = info.exists();
151151
info.erase();
152152

153-
if (pending_send_ids.erase(std::string(session_id)) > 0)
154-
_needs_dump = true;
153+
set_pending_send(std::string(session_id), false);
155154

156155
return ret;
157156
}
@@ -162,6 +161,20 @@ size_t Members::size() const {
162161
return 0;
163162
}
164163

164+
bool Members::has_pending_send(std::string pubkey_hex) const {
165+
return pending_send_ids.count(pubkey_hex);
166+
}
167+
168+
void Members::set_pending_send(std::string pubkey_hex, bool pending) {
169+
bool changed = false;
170+
if (pending)
171+
changed = pending_send_ids.insert(pubkey_hex).second;
172+
else
173+
changed = pending_send_ids.erase(pubkey_hex);
174+
if(changed)
175+
_needs_dump = true;
176+
}
177+
165178
member::member(std::string sid) : session_id{std::move(sid)} {
166179
check_session_id(session_id);
167180
}

tests/test_config_userprofile.cpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ void log_msg(config_log_level lvl, const char* msg, void*) {
2121
<< ": " << msg);
2222
}
2323

24+
auto empty_extra_data = "1:+de";
25+
2426
TEST_CASE("UserProfile", "[config][user_profile]") {
2527

2628
const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes;
@@ -221,7 +223,7 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") {
221223
"1:!" "i2e"
222224
"1:$" + std::to_string(exp_push1_decrypted.size()) + ":" + std::string{to_sv(exp_push1_decrypted)} + ""
223225
"1:(" "0:"
224-
"1:)" "le"
226+
"1:)" "le" + empty_extra_data +
225227
"e"));
226228
// clang-format on
227229
free(dump1); // done with the dump; don't leak!
@@ -240,7 +242,7 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") {
240242
"1:!" "i0e"
241243
"1:$" + std::to_string(exp_push1_decrypted.size()) + ":" + std::string{to_sv(exp_push1_decrypted)} + ""
242244
"1:(" "9:fakehash1"
243-
"1:)" "le"
245+
"1:)" "le" + empty_extra_data +
244246
"e"));
245247
// clang-format on
246248
free(dump1);

tests/test_group_members.cpp

+44
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ TEST_CASE("Group Members", "[config][groups][members]") {
138138
CHECK(gmem2.get_status(m) ==
139139
session::config::groups::member::Status::promotion_accepted);
140140
} else {
141+
// on gmem1, our local extra data marks m as invite_sending
142+
CHECK(gmem1.get_status(m) ==
143+
session::config::groups::member::Status::invite_sending);
144+
// that extra data is not pushed, so gmem2 doesn't know about it
141145
CHECK(gmem2.get_status(m) ==
142146
session::config::groups::member::Status::invite_not_sent);
143147
CHECK_FALSE(m.admin);
@@ -330,3 +334,43 @@ TEST_CASE("Group Members", "[config][groups][members]") {
330334
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"
331335
"901234567890");
332336
}
337+
338+
TEST_CASE("Group Members restores extra data", "[config][groups][members]") {
339+
340+
const auto seed = "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"_hexbytes;
341+
std::array<unsigned char, 32> ed_pk;
342+
std::array<unsigned char, 64> ed_sk;
343+
crypto_sign_ed25519_seed_keypair(
344+
ed_pk.data(), ed_sk.data(), reinterpret_cast<const unsigned char*>(seed.data()));
345+
346+
REQUIRE(oxenc::to_hex(ed_pk.begin(), ed_pk.end()) ==
347+
"cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece");
348+
CHECK(oxenc::to_hex(seed.begin(), seed.end()) ==
349+
oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32));
350+
351+
groups::Members gmem1{to_usv(ed_pk), to_usv(ed_sk), std::nullopt};
352+
353+
auto memberId1 = "050000000000000000000000000000000000000000000000000000000000000000";
354+
auto memberId2 = "051111111111111111111111111111111111111111111111111111111111111111";
355+
356+
auto member1 = gmem1.get_or_construct(memberId1);
357+
auto member2 = gmem1.get_or_construct(memberId2);
358+
359+
member2.set_promoted();
360+
gmem1.set(member1); // should be marked as "invite sending" right away
361+
gmem1.set(member2); // should be marked as "promotion sending" right away
362+
363+
CHECK(gmem1.get_status(gmem1.get_or_construct(memberId1)) ==
364+
groups::member::Status::invite_sending);
365+
CHECK(gmem1.get_status(gmem1.get_or_construct(memberId2)) ==
366+
groups::member::Status::promotion_sending);
367+
368+
auto dumped = gmem1.dump();
369+
370+
groups::Members gmem2{to_usv(ed_pk), to_usv(ed_sk), dumped};
371+
372+
CHECK(gmem2.get_status(gmem1.get_or_construct(memberId1)) ==
373+
groups::member::Status::invite_sending);
374+
CHECK(gmem2.get_status(gmem1.get_or_construct(memberId2)) ==
375+
groups::member::Status::promotion_sending);
376+
}

0 commit comments

Comments
 (0)