Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/groups/meta_group_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class MetaGroupWrapper : public Napi::ObjectWrap<MetaGroupWrapper> {
/* Shared Actions */
Napi::Value needsPush(const Napi::CallbackInfo& info);
Napi::Value push(const Napi::CallbackInfo& info);
Napi::Value pushForRecovery(const Napi::CallbackInfo& info);
Napi::Value needsDump(const Napi::CallbackInfo& info);
Napi::Value metaDump(const Napi::CallbackInfo& info);
Napi::Value metaMakeDump(const Napi::CallbackInfo& info);
Expand Down Expand Up @@ -84,6 +85,7 @@ class MetaGroupWrapper : public Napi::ObjectWrap<MetaGroupWrapper> {
Napi::Value loadKeyMessage(const Napi::CallbackInfo& info);
Napi::Value keyGetCurrentGen(const Napi::CallbackInfo& info);
Napi::Value activeHashes(const Napi::CallbackInfo& info);
Napi::Value activeHashesByConfig(const Napi::CallbackInfo& info);
Napi::Value encryptMessages(const Napi::CallbackInfo& info);
Napi::Value decryptMessage(const Napi::CallbackInfo& info);
Napi::Value makeSwarmSubAccount(const Napi::CallbackInfo& info);
Expand Down
69 changes: 69 additions & 0 deletions src/groups/meta_group_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ void MetaGroupWrapper::Init(Napi::Env env, Napi::Object exports) {
// shared exposed functions
InstanceMethod("needsPush", &MetaGroupWrapper::needsPush),
InstanceMethod("push", &MetaGroupWrapper::push),
InstanceMethod("pushForRecovery", &MetaGroupWrapper::pushForRecovery),
InstanceMethod("needsDump", &MetaGroupWrapper::needsDump),
InstanceMethod("metaDump", &MetaGroupWrapper::metaDump),
InstanceMethod("metaMakeDump", &MetaGroupWrapper::metaMakeDump),
Expand Down Expand Up @@ -142,6 +143,7 @@ void MetaGroupWrapper::Init(Napi::Env env, Napi::Object exports) {
InstanceMethod(
"keyGetEncryptionKeyHex", &MetaGroupWrapper::keyGetEncryptionKeyHex),
InstanceMethod("activeHashes", &MetaGroupWrapper::activeHashes),
InstanceMethod("activeHashesByConfig", &MetaGroupWrapper::activeHashesByConfig),
InstanceMethod("loadKeyMessage", &MetaGroupWrapper::loadKeyMessage),
InstanceMethod("keyGetCurrentGen", &MetaGroupWrapper::keyGetCurrentGen),
InstanceMethod("encryptMessages", &MetaGroupWrapper::encryptMessages),
Expand Down Expand Up @@ -199,6 +201,44 @@ Napi::Value MetaGroupWrapper::push(const Napi::CallbackInfo& info) {
});
}

/**
* The current serialised bytes of GroupInfo and GroupMembers, whether or not they need pushing.
*
* This exists for ONE caller: putting a config back on the swarm after it expired from there. Do
* not reach for it when syncing — use push(), which returns null for a sub-config with nothing to
* send and is what every normal code path wants. Emitting a clean config during a sync would be
* pure redundant traffic, and would drain that config's obsolete-hash list as a side effect.
*
* Two things a caller has to know:
*
* - This still goes through ConfigBase::push(), which drains the config's obsolete-hash list and
* clears it unconditionally. Those hashes come back in `hashes` and are handed over exactly
* once, so a caller that ignores them loses them permanently and leaves the superseded messages
* on the swarm forever. Issue the delete for them like a normal push does.
* Note the drain is skipped for a read-only config (a group member), so an empty list there is
* the expected result rather than a sign anything went wrong — and a member holds no Delete
* permission to act on them with anyway.
* - GroupKeys is deliberately absent. There is no way to re-emit a stored keys message:
* groups::Keys has no push(), and pending_config() is only non-null while a rekey is in
* progress. An expired keys message can only be replaced by an admin rekeying.
*/
Napi::Value MetaGroupWrapper::pushForRecovery(const Napi::CallbackInfo& info) {
return wrapResult(info, [&] {
auto env = info.Env();
auto to_push = Napi::Object::New(env);

to_push["groupMember"s] = push_result_to_JS(
env,
this->meta_group->members->push(),
this->meta_group->members->storage_namespace());

to_push["groupInfo"s] = push_result_to_JS(
env, this->meta_group->info->push(), this->meta_group->info->storage_namespace());

return to_push;
});
}

Napi::Value MetaGroupWrapper::needsDump(const Napi::CallbackInfo& info) {
return wrapResult(info, [&] {
return this->meta_group->members->needs_dump() || this->meta_group->info->needs_dump() ||
Expand Down Expand Up @@ -786,6 +826,35 @@ Napi::Value MetaGroupWrapper::activeHashes(const Napi::CallbackInfo& info) {
});
}

/**
* The same hashes as activeHashes(), but kept separate per sub-config.
*
* activeHashes() concatenates all three, which is fine for bumping TTLs — the caller just needs
* the full set. It is not enough to act on a *specific* hash, though: once the storage server
* reports one missing, "which config was that?" decides what happens next, and the three answers
* are very different. A missing GroupInfo or GroupMembers hash can be put back by re-storing the
* config; a missing GroupKeys hash cannot be put back by anyone but an admin performing a rekey.
*/
Napi::Value MetaGroupWrapper::activeHashesByConfig(const Napi::CallbackInfo& info) {
return wrapResult(info, [&] {
auto env = info.Env();
auto obj = Napi::Object::New(env);

auto keysHashes = meta_group->keys->active_hashes();
auto infoHashes = meta_group->info->active_hashes();
auto memberHashes = meta_group->members->active_hashes();

obj["groupKeys"s] =
toJs(env, std::vector<std::string>{keysHashes.begin(), keysHashes.end()});
obj["groupInfo"s] =
toJs(env, std::vector<std::string>{infoHashes.begin(), infoHashes.end()});
obj["groupMember"s] =
toJs(env, std::vector<std::string>{memberHashes.begin(), memberHashes.end()});

return obj;
});
}

Napi::Value MetaGroupWrapper::encryptMessages(const Napi::CallbackInfo& info) {
return wrapResult(info, [&] {
assertInfoLength(info, 1);
Expand Down
43 changes: 42 additions & 1 deletion types/groups/metagroup.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,34 @@ declare module 'libsession_util_nodejs' {
namespace: number;
} | null;
};
/**
* The current bytes of groupInfo and groupMember whether or not they need pushing, for
* putting a config back on the swarm after it expired from there.
*
* Use `push()` for syncing — this one deliberately ignores `needsPush()`, so using it to
* sync would send configs that haven't changed. `hashes` still carries the config's
* obsolete hashes and is still handed over only once, so the caller must issue the delete
* for them (expect an empty list on a read-only config — a group member — which is normal).
*
* groupKeys is absent because a stored keys message cannot be re-emitted at all; only an
* admin rekey can replace one.
*/
pushForRecovery: () => {
groupInfo: PushConfigResult;
groupMember: PushConfigResult;
};
/**
* The same hashes as `activeHashes()`, kept separate per sub-config.
*
* `activeHashes()` merges all three, which is enough to bump TTLs but not to act on a
* specific hash: a missing groupInfo/groupMember hash can be re-stored, a missing groupKeys
* hash can only be replaced by an admin rekey.
*/
activeHashesByConfig: () => {
groupInfo: Array<string>;
groupMember: Array<string>;
groupKeys: Array<string>;
};
needsDump: () => boolean;
metaDump: () => Uint8Array;
metaMakeDump: () => Uint8Array;
Expand All @@ -52,6 +80,15 @@ declare module 'libsession_util_nodejs' {
groupInfo: ConfirmPush | null;
groupMember: ConfirmPush | null;
}) => void;
/**
* @returns how many of the messages handed in were merged. Compare it against what you
* passed: libSession skips a config message it cannot take and carries on without erroring,
* so a partial merge is invisible to a caller that only checks for a throw.
*
* Note the keys count is optimistic — a keys message is counted even when it turns out not
* to be encrypted to us, which is not a failure. So this detects a lossy groupInfo or
* groupMember merge, which is what matters.
*/
metaMerge: ({
groupInfo,
groupKeys,
Expand All @@ -60,7 +97,7 @@ declare module 'libsession_util_nodejs' {
groupInfo: Array<MergeSingle> | null;
groupMember: Array<MergeSingle> | null;
groupKeys: Array<MergeSingle & { timestampMs: number }> | null;
}) => void;
}) => number;
Comment thread
Bilb marked this conversation as resolved.
};

// this just adds an argument of type GroupPubkeyType in front of the parameters of that function
Expand All @@ -78,12 +115,14 @@ declare module 'libsession_util_nodejs' {
// shared actions
public needsPush: MetaGroupWrapper['needsPush'];
public push: MetaGroupWrapper['push'];
public pushForRecovery: MetaGroupWrapper['pushForRecovery'];
public needsDump: MetaGroupWrapper['needsDump'];
public metaDump: MetaGroupWrapper['metaDump'];
public metaMakeDump: MetaGroupWrapper['metaMakeDump'];
public metaConfirmPushed: MetaGroupWrapper['metaConfirmPushed'];
public metaMerge: MetaGroupWrapper['metaMerge'];
public activeHashes: MetaGroupWrapper['activeHashes'];
public activeHashesByConfig: MetaGroupWrapper['activeHashesByConfig'];

// info
public infoGet: MetaGroupWrapper['infoGet'];
Expand Down Expand Up @@ -128,6 +167,7 @@ declare module 'libsession_util_nodejs' {
// shared actions
| MakeActionCall<MetaGroupWrapper, 'needsPush'>
| MakeActionCall<MetaGroupWrapper, 'push'>
| MakeActionCall<MetaGroupWrapper, 'pushForRecovery'>
| MakeActionCall<MetaGroupWrapper, 'needsDump'>
| MakeActionCall<MetaGroupWrapper, 'metaDump'>
| MakeActionCall<MetaGroupWrapper, 'metaMakeDump'>
Expand Down Expand Up @@ -169,6 +209,7 @@ declare module 'libsession_util_nodejs' {
| MakeActionCall<MetaGroupWrapper, 'keysAdmin'>
| MakeActionCall<MetaGroupWrapper, 'keyGetCurrentGen'>
| MakeActionCall<MetaGroupWrapper, 'activeHashes'>
| MakeActionCall<MetaGroupWrapper, 'activeHashesByConfig'>
| MakeActionCall<MetaGroupWrapper, 'encryptMessages'>
| MakeActionCall<MetaGroupWrapper, 'decryptMessage'>
| MakeActionCall<MetaGroupWrapper, 'makeSwarmSubAccount'>
Expand Down
Loading