Skip to content

Commit a4ac5db

Browse files
committed
add pending_state get/set and fix unit tests
1 parent b7427ae commit a4ac5db

File tree

5 files changed

+101
-10
lines changed

5 files changed

+101
-10
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

+25-4
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,29 @@ 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+
///
348+
/// Inputs:
349+
/// - `pubkey_hex` -- hex string of the session id
350+
/// - `pending` -- pending send state to set for that member
351+
///
352+
/// Outputs:
353+
/// - `bool` - true if a change was made.
354+
bool set_pending_send(std::string pubkey_hex, bool pending);
355+
333356
/// API: groups/Members::get_status
334357
///
335358
/// This function goes through the various status values and returns a single consolidated
@@ -351,8 +374,7 @@ class Members : public ConfigBase {
351374

352375
// If the member is promoted then we return the relevant promoted status
353376
if (member.admin) {
354-
if (member.promotion_status == STATUS_NOT_SENT &&
355-
pending_send_ids.find(member.session_id) != pending_send_ids.end())
377+
if (member.promotion_status == STATUS_NOT_SENT && has_pending_send(member.session_id))
356378
return member::Status::promotion_sending;
357379
else if (member.promotion_status == STATUS_NOT_SENT)
358380
return member::Status::promotion_not_sent;
@@ -367,8 +389,7 @@ class Members : public ConfigBase {
367389
}
368390

369391
// 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())
392+
if (member.invite_status == STATUS_NOT_SENT && has_pending_send(member.session_id))
372393
return member::Status::invite_sending;
373394
else if (member.invite_status == STATUS_NOT_SENT)
374395
return member::Status::invite_not_sent;

src/config/groups/members.cpp

+14-3
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,7 +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)
153+
if (set_pending_send(std::string(session_id), false))
154154
_needs_dump = true;
155155

156156
return ret;
@@ -162,6 +162,17 @@ size_t Members::size() const {
162162
return 0;
163163
}
164164

165+
bool Members::has_pending_send(std::string pubkey_hex) const {
166+
return pending_send_ids.count(pubkey_hex);
167+
}
168+
169+
bool Members::set_pending_send(std::string pubkey_hex, bool pending) {
170+
if (pending)
171+
return pending_send_ids.insert(pubkey_hex).second;
172+
else
173+
return pending_send_ids.erase(pubkey_hex);
174+
}
175+
165176
member::member(std::string sid) : session_id{std::move(sid)} {
166177
check_session_id(session_id);
167178
}

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)