Skip to content

Commit 12d1723

Browse files
authored
Merge pull request #45 from session-foundation/feat/use-bitsets-for-profeatures
feat: add bitset for proFeaturesOfMessage & rotatingKeyGeneration
2 parents d062724 + 27285d1 commit 12d1723

File tree

11 files changed

+184
-236
lines changed

11 files changed

+184
-236
lines changed

include/pro/pro.hpp

Lines changed: 12 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <napi.h>
34
#include <oxenc/base64.h>
45
#include <oxenc/hex.h>
56

@@ -110,22 +111,6 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
110111
"proStatusRequestBody",
111112
static_cast<napi_property_attributes>(
112113
napi_writable | napi_configurable)),
113-
114-
// Note: those are not plugged in for now as we do this parsing through zod
115-
// on desktop.
116-
// Pro responses parsing
117-
// StaticMethod<&ProWrapper::proProofParseResponse>(
118-
// "proProofParseResponse",
119-
// static_cast<napi_property_attributes>(
120-
// napi_writable | napi_configurable)),
121-
// StaticMethod<&ProWrapper::proRevocationsParseResponse>(
122-
// "proRevocationsParseResponse",
123-
// static_cast<napi_property_attributes>(
124-
// napi_writable | napi_configurable)),
125-
// StaticMethod<&ProWrapper::proStatusParseResponse>(
126-
// "proStatusParseResponse",
127-
// static_cast<napi_property_attributes>(
128-
// napi_writable | napi_configurable)),
129114
});
130115
}
131116

@@ -135,7 +120,7 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
135120
// we expect two arguments that match:
136121
// first: {
137122
// "utf16": string,
138-
// "proFeatures": Array<ProFeature>,
123+
// "proFeaturesBitset": bigint,
139124
// }
140125

141126
assertInfoLength(info, 1);
@@ -147,27 +132,13 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
147132
if (first.IsEmpty())
148133
throw std::invalid_argument("proFeaturesForMessage first received empty");
149134

150-
assertIsArray(first.Get("proFeatures"), "proFeaturesForMessage.proFeatures");
151-
auto proFeaturesJS = first.Get("proFeatures").As<Napi::Array>();
152-
std::vector<std::string> proFeatures;
153-
proFeatures.reserve(proFeaturesJS.Length());
154-
for (uint32_t i = 0; i < proFeaturesJS.Length(); i++) {
155-
auto itemValue = proFeaturesJS.Get(i);
156-
assertIsString(itemValue, "proFeaturesForMessage.proFeatures.itemValue");
157-
std::string item =
158-
toCppString(itemValue, "proFeaturesForMessage.proFeatures.itemValue");
159-
proFeatures.push_back(item);
160-
}
161-
162-
SESSION_PROTOCOL_PRO_EXTRA_FEATURES flags = 0;
163-
for (std::string& feature : proFeatures) {
164-
// Note: 10K_CHARACTER_LIMIT cannot be requested by the caller
165-
if (feature == "PRO_BADGE") {
166-
flags |= SESSION_PROTOCOL_PRO_EXTRA_FEATURES_PRO_BADGE;
167-
} else if (feature == "ANIMATED_AVATAR") {
168-
flags |= SESSION_PROTOCOL_PRO_EXTRA_FEATURES_ANIMATED_AVATAR;
169-
}
170-
}
135+
assertIsBigint(
136+
first.Get("proFeaturesBitset"), "proFeaturesForMessage.proFeaturesBitset");
137+
138+
auto lossless = true;
139+
SESSION_PROTOCOL_PRO_FEATURES flags =
140+
first.Get("proFeaturesBitset").As<Napi::BigInt>().Uint64Value(&lossless);
141+
171142
assertIsString(first.Get("utf16"), "proFeaturesForMessage.utf16");
172143
std::u16string utf16 = first.Get("utf16").As<Napi::String>().Utf16Value();
173144
auto pro_features_msg =
@@ -179,7 +150,7 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
179150
obj["error"] =
180151
pro_features_msg.error.size() ? toJs(env, pro_features_msg.error) : env.Null();
181152
obj["codepointCount"] = toJs(env, pro_features_msg.codepoint_count);
182-
obj["proFeatures"] = proFeaturesToJs(env, pro_features_msg.features);
153+
obj["proFeaturesBitset"] = proFeaturesToJsBitset(env, pro_features_msg.features);
183154

184155
return obj;
185156
});
@@ -219,9 +190,6 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
219190
std::string rotating_privkey =
220191
toCppString(rotating_privkey_js, "proProofRequestBody.rotatingPrivKeyHex");
221192

222-
assert_length(master_privkey, 64, "masterPrivKeyHex");
223-
assert_length(rotating_privkey, 64, "rotatingPrivkey");
224-
225193
auto master_privkey_decoded = from_hex(master_privkey);
226194
auto rotating_privkey_decoded = from_hex(rotating_privkey);
227195

@@ -235,33 +203,6 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
235203
});
236204
};
237205

238-
static Napi::Value proProofParseResponse(const Napi::CallbackInfo& info) {
239-
return wrapResult(info, [&] {
240-
// we expect arguments that match:
241-
// first: {
242-
// "json": string,
243-
// }
244-
245-
assertInfoLength(info, 1);
246-
assertIsObject(info[0]);
247-
auto env = info.Env();
248-
249-
auto first = info[0].As<Napi::Object>();
250-
251-
if (first.IsEmpty())
252-
throw std::invalid_argument("proProofParseResponse first received empty");
253-
254-
assertIsString(first.Get("json"), "proProofParseResponse.json");
255-
auto json_str = toCppString(first.Get("json"), "proProofParseResponse.json");
256-
auto parsed = pro_backend::AddProPaymentOrGetProProofResponse::parse(json_str);
257-
258-
auto obj = toJs(env, static_cast<pro_backend::ResponseHeader>(parsed));
259-
obj["proof"] = toJsOrNullIfErrors(env, parsed.proof, parsed.errors);
260-
261-
return obj;
262-
});
263-
};
264-
265206
static Napi::Value proRevocationsRequestBody(const Napi::CallbackInfo& info) {
266207
return wrapResult(info, [&] {
267208
// we expect arguments that match:
@@ -293,35 +234,6 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
293234
});
294235
};
295236

296-
static Napi::Value proRevocationsParseResponse(const Napi::CallbackInfo& info) {
297-
return wrapResult(info, [&] {
298-
// we expect arguments that match:
299-
// first: {
300-
// "json": string,
301-
// }
302-
303-
assertInfoLength(info, 1);
304-
assertIsObject(info[0]);
305-
auto env = info.Env();
306-
307-
auto first = info[0].As<Napi::Object>();
308-
309-
if (first.IsEmpty())
310-
throw std::invalid_argument("proRevocationsParseResponse first received empty");
311-
312-
assertIsString(first.Get("json"), "proRevocationsParseResponse.json");
313-
auto json_str = toCppString(first.Get("json"), "proRevocationsParseResponse.json");
314-
auto parsed = pro_backend::GetProRevocationsResponse::parse(json_str);
315-
316-
auto obj = toJs(env, static_cast<pro_backend::ResponseHeader>(parsed));
317-
// if error is set, the body might not be parsable so don't try to use it
318-
obj["ticket"] = parsed.errors.size() ? env.Null() : toJs(env, parsed.ticket);
319-
obj["items"] = parsed.errors.size() ? env.Null() : toJs(env, parsed.items);
320-
321-
return obj;
322-
});
323-
};
324-
325237
static Napi::Value proStatusRequestBody(const Napi::CallbackInfo& info) {
326238
return wrapResult(info, [&] {
327239
// we expect arguments that match:
@@ -355,55 +267,17 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
355267
auto master_privkey =
356268
toCppString(master_privkey_js, "proStatusRequestBody.masterPrivKeyHex");
357269

358-
assert_length(master_privkey, 64, "proStatusRequestBody.masterPrivKeyHex");
270+
auto master_privkey_decoded = from_hex(master_privkey);
359271

360272
auto json = pro_backend::GetProStatusRequest::build_to_json(
361273
static_cast<uint8_t>(requestVersion.Int32Value()),
362-
to_span(from_hex(master_privkey)),
274+
to_span(master_privkey_decoded),
363275
unix_ts_ms,
364276
withPaymentHistory);
365277

366278
return json;
367279
});
368280
};
369-
370-
static Napi::Value proStatusParseResponse(const Napi::CallbackInfo& info) {
371-
return wrapResult(info, [&] {
372-
// we expect arguments that match:
373-
// first: {
374-
// "json": string,
375-
// }
376-
377-
assertInfoLength(info, 1);
378-
assertIsObject(info[0]);
379-
auto env = info.Env();
380-
381-
auto first = info[0].As<Napi::Object>();
382-
383-
if (first.IsEmpty())
384-
throw std::invalid_argument("proStatusParseResponse first received empty");
385-
386-
assertIsString(first.Get("json"), "proStatusParseResponse.json");
387-
auto json_str = toCppString(first.Get("json"), "proStatusParseResponse.json");
388-
auto parsed = pro_backend::GetProStatusResponse::parse(json_str);
389-
390-
auto obj = toJs(env, static_cast<pro_backend::ResponseHeader>(parsed));
391-
392-
obj["items"] = toJsOrNullIfErrors(env, parsed.items, parsed.errors);
393-
obj["userStatus"] = toJsOrNullIfErrors(
394-
env, proBackendEnumToString(parsed.user_status), parsed.errors);
395-
396-
obj["errorReport"] = toJsOrNullIfErrors(
397-
env, proBackendEnumToString(parsed.error_report), parsed.errors);
398-
399-
obj["autoRenewing"] = toJsOrNullIfErrors(env, parsed.auto_renewing, parsed.errors);
400-
obj["expiryTsMs"] = toJsOrNullIfErrors(env, parsed.expiry_unix_ts_ms, parsed.errors);
401-
obj["gracePeriodMs"] =
402-
toJsOrNullIfErrors(env, parsed.grace_period_duration_ms, parsed.errors);
403-
404-
return obj;
405-
});
406-
};
407281
};
408282

409283
}; // namespace session::nodeapi

include/pro/types.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct toJs_impl<session::ProProof> {
1919
obj["genIndexHashB64"] = toJs(env, oxenc::to_base64(pro_proof.gen_index_hash));
2020
obj["rotatingPubkeyHex"] = toJs(env, oxenc::to_hex(pro_proof.rotating_pubkey));
2121
obj["expiryMs"] = toJs(env, pro_proof.expiry_unix_ts.time_since_epoch().count());
22+
obj["signatureHex"] = toJs(env, oxenc::to_hex(pro_proof.sig));
2223

2324
return obj;
2425
}
@@ -77,7 +78,7 @@ struct toJs_impl<session::DecodedPro> {
7778
: decoded_pro.status == ProStatus::Valid ? "Valid"
7879
: "Expired");
7980
obj["proProof"] = toJs(env, decoded_pro.proof);
80-
obj["proFeatures"] = proFeaturesToJs(env, decoded_pro.features);
81+
obj["proFeaturesBitset"] = proFeaturesToJsBitset(env, decoded_pro.features);
8182

8283
return obj;
8384
}

include/user_config.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <napi.h>
44

55
#include "base_config.hpp"
6+
#include "session/config/pro.hpp"
67
#include "session/config/user_profile.hpp"
78
#include "utilities.hpp"
89

@@ -15,6 +16,10 @@ class UserConfigWrapper : public ConfigBaseImpl, public Napi::ObjectWrap<UserCon
1516
explicit UserConfigWrapper(const Napi::CallbackInfo& info);
1617

1718
private:
19+
// FIXME: wrap those in the extra data field of UserConfig instead
20+
std::optional<config::ProConfig> pro_config;
21+
int64_t pro_user_features = 0;
22+
1823
config::UserProfile& config{get_config<config::UserProfile>()};
1924

2025
Napi::Value getPriority(const Napi::CallbackInfo& info);
@@ -37,6 +42,10 @@ class UserConfigWrapper : public ConfigBaseImpl, public Napi::ObjectWrap<UserCon
3742

3843
Napi::Value getProConfig(const Napi::CallbackInfo& info);
3944
void setProConfig(const Napi::CallbackInfo& info);
45+
Napi::Value getProFeaturesBitset(const Napi::CallbackInfo& info);
46+
void setProFeaturesBitset(const Napi::CallbackInfo& info);
47+
4048
Napi::Value generateProMasterKey(const Napi::CallbackInfo& info);
49+
Napi::Value generateRotatingPrivKeyHex(const Napi::CallbackInfo& info);
4150
};
4251
}; // namespace session::nodeapi

include/utilities.hpp

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ void assertInfoMinLength(const Napi::CallbackInfo& info, const int minLength);
4343

4444
void assertIsStringOrNull(const Napi::Value& value, const std::string& identifier = "");
4545
void assertIsNumber(const Napi::Value& value, const std::string& identifier);
46+
void assertIsBigint(const Napi::Value& val, const std::string& identifier);
4647
void assertIsArray(const Napi::Value& value, const std::string& identifier);
4748
void assertIsObject(const Napi::Value& value);
4849
void assertIsUInt8ArrayOrNull(const Napi::Value& value);
@@ -72,6 +73,12 @@ std::span<const unsigned char> toCppBufferView(Napi::Value x, const std::string&
7273
std::vector<unsigned char> toCppBuffer(Napi::Value x, const std::string& identifier);
7374

7475
int64_t toCppInteger(Napi::Value x, const std::string& identifier, bool allowUndefined = false);
76+
77+
/**
78+
* Same as toCppInteger, but for BigInt
79+
*/
80+
int64_t toCppIntegerB(Napi::Value x, const std::string& identifier, bool allowUndefined = false);
81+
7582
std::optional<int64_t> maybeNonemptyInt(Napi::Value x, const std::string& identifier);
7683
std::optional<bool> maybeNonemptyBoolean(Napi::Value x, const std::string& identifier);
7784
std::optional<std::chrono::sys_seconds> maybeNonemptySysSeconds(
@@ -394,7 +401,8 @@ Napi::Object decrypt_result_to_JS(
394401

395402
confirm_pushed_entry_t confirm_pushed_entry_from_JS(const Napi::Env& env, const Napi::Object& obj);
396403

397-
Napi::Object proFeaturesToJs(const Napi::Env& env, const SESSION_PROTOCOL_PRO_FEATURES bitset);
404+
Napi::BigInt proFeaturesToJsBitset(
405+
const Napi::Env& env, const SESSION_PROTOCOL_PRO_FEATURES bitset);
398406

399407
std::span<const uint8_t> from_hex_to_span(std::string_view x);
400408

@@ -405,10 +413,11 @@ template <std::size_t N>
405413
std::array<uint8_t, N> from_hex_to_array(std::string x) {
406414
std::string as_hex = oxenc::from_hex(x);
407415
if (as_hex.size() != N) {
408-
throw std::invalid_argument(fmt::format(
409-
"from_hex_to_array: Decoded hex size mismatch: expected {}, got {}",
410-
N,
411-
as_hex.size()));
416+
throw std::invalid_argument(
417+
fmt::format(
418+
"from_hex_to_array: Decoded hex size mismatch: expected {}, got {}",
419+
N,
420+
as_hex.size()));
412421
}
413422

414423
std::array<uint8_t, N> result;
@@ -424,16 +433,15 @@ std::vector<unsigned char> from_base64_to_vector(std::string_view x);
424433
// Concept to match containers with a size() method
425434
template <typename T>
426435
concept HasSize = requires(T t) {
427-
{
428-
t.size()
429-
} -> std::convertible_to<size_t>;
436+
{t.size()}->std::convertible_to<size_t>;
430437
};
431438

432439
template <HasSize T>
433440
void assert_length(const T& x, size_t n, std::string_view base_identifier) {
434441
if (x.size() != n) {
435-
throw std::invalid_argument(fmt::format(
436-
"assert_length: expected {}, got {} for {}", n, x.size(), base_identifier));
442+
throw std::invalid_argument(
443+
fmt::format(
444+
"assert_length: expected {}, got {} for {}", n, x.size(), base_identifier));
437445
}
438446
}
439447

0 commit comments

Comments
 (0)