@derec-alliance/react-native 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -15
- package/android/CMakeLists.txt +1 -1
- package/android/src/main/jniLibs/arm64-v8a/libderec_library.a +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libderec_library.a +0 -0
- package/android/src/main/jniLibs/x86_64/libderec_library.a +0 -0
- package/cpp/Convert.cpp +125 -0
- package/cpp/Convert.h +35 -0
- package/cpp/DeRecInstaller.cpp +0 -12
- package/cpp/Primitives.cpp +159 -26
- package/cpp/Primitives.h +16 -0
- package/cpp/ProtocolHost.cpp +22 -47
- package/cpp/StoreCallbacks.cpp +97 -304
- package/cpp/UserSecretsJson.cpp +109 -0
- package/cpp/UserSecretsJson.h +28 -0
- package/cpp/derec_ffi.h +256 -120
- package/ios/DeRecFFI.xcframework/ios-arm64/Headers/derec_ffi.h +256 -120
- package/ios/DeRecFFI.xcframework/ios-arm64/libderec_library.a +0 -0
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/Headers/derec_ffi.h +256 -120
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/libderec_library.a +2 -2
- package/lib/errors.d.ts +7 -0
- package/lib/native.d.ts +5 -1
- package/lib/primitives.d.ts +18 -21
- package/lib/primitives.js +48 -25
- package/lib/protocol.d.ts +49 -97
- package/lib/protocol.js +71 -162
- package/lib/types.d.ts +218 -107
- package/lib/types.js +43 -13
- package/package.json +1 -1
- package/src/errors.ts +7 -0
- package/src/native.ts +2 -1
- package/src/primitives.ts +82 -25
- package/src/protocol.ts +79 -179
- package/src/types.ts +232 -96
package/cpp/ProtocolHost.cpp
CHANGED
|
@@ -26,7 +26,12 @@ namespace {
|
|
|
26
26
|
/// failure.
|
|
27
27
|
struct FfiFailure {
|
|
28
28
|
explicit FfiFailure(DeRecError e) : error(e) {}
|
|
29
|
+
FfiFailure(DeRecError e, std::vector<uint8_t> channelIdsJson)
|
|
30
|
+
: error(e), conflictingChannelIdsJson(std::move(channelIdsJson)) {}
|
|
29
31
|
DeRecError error;
|
|
32
|
+
/// `restore`'s JSON array of colliding channel ids, empty for every other
|
|
33
|
+
/// failure. Carried as raw bytes: parsing needs the runtime.
|
|
34
|
+
std::vector<uint8_t> conflictingChannelIdsJson;
|
|
30
35
|
};
|
|
31
36
|
|
|
32
37
|
constexpr const char* kReentrancyMessage =
|
|
@@ -170,10 +175,12 @@ jsi::Value ProtocolHost::runAsync(
|
|
|
170
175
|
bool failed = false;
|
|
171
176
|
bool unknownFailure = false;
|
|
172
177
|
DeRecError capturedError{};
|
|
178
|
+
std::vector<uint8_t> capturedChannelIdsJson;
|
|
173
179
|
try {
|
|
174
180
|
bytes = body();
|
|
175
|
-
} catch (
|
|
181
|
+
} catch (FfiFailure& failure) {
|
|
176
182
|
capturedError = failure.error;
|
|
183
|
+
capturedChannelIdsJson = std::move(failure.conflictingChannelIdsJson);
|
|
177
184
|
failed = true;
|
|
178
185
|
} catch (...) {
|
|
179
186
|
// Anything other than `FfiFailure` — `std::bad_alloc` from
|
|
@@ -198,7 +205,9 @@ jsi::Value ProtocolHost::runAsync(
|
|
|
198
205
|
|
|
199
206
|
invoker->invokeAsync([state, runtime, resolve, reject, convert,
|
|
200
207
|
bytes = std::move(bytes), failed,
|
|
201
|
-
unknownFailure, capturedError
|
|
208
|
+
unknownFailure, capturedError,
|
|
209
|
+
capturedChannelIdsJson =
|
|
210
|
+
std::move(capturedChannelIdsJson)]() mutable {
|
|
202
211
|
if (state->runtimeInvalidated.load()) {
|
|
203
212
|
// Queued before the runtime was torn down, reached only
|
|
204
213
|
// afterwards. Every JSI call below — including the ones the
|
|
@@ -212,7 +221,7 @@ jsi::Value ProtocolHost::runAsync(
|
|
|
212
221
|
// Reuses the exact error shape every synchronous FFI
|
|
213
222
|
// wrapper throws, rather than inventing a second one for
|
|
214
223
|
// the asynchronous path.
|
|
215
|
-
throwDeRecError(jsRt, capturedError);
|
|
224
|
+
throwDeRecError(jsRt, capturedError, capturedChannelIdsJson);
|
|
216
225
|
}
|
|
217
226
|
if (unknownFailure) {
|
|
218
227
|
throw jsi::JSError(jsRt, "DeRec: an unexpected native error occurred");
|
|
@@ -269,9 +278,8 @@ jsi::Value ProtocolHost::get(jsi::Runtime& rt, const jsi::PropNameID& name) {
|
|
|
269
278
|
size_t) -> jsi::Value { return hostSecretId(rt2, secretId_); });
|
|
270
279
|
}
|
|
271
280
|
|
|
272
|
-
// `setCommunicationInfo` and `
|
|
273
|
-
// `@derec-alliance/nodejs
|
|
274
|
-
// the JavaScript thread: `derec_protocol_set_*` takes the same handle mutex
|
|
281
|
+
// `setCommunicationInfo` and `setOwnTransports` return a Promise, as in
|
|
282
|
+
// `@derec-alliance/nodejs`. They must not run on the JavaScript thread: `derec_protocol_set_*` takes the same handle mutex
|
|
275
283
|
// that `derec_protocol_process`/`start`/`restore` hold for the whole
|
|
276
284
|
// duration of a flow, including across store callbacks that park the worker
|
|
277
285
|
// waiting on the JavaScript thread. Calling the setter synchronously would
|
|
@@ -281,9 +289,7 @@ jsi::Value ProtocolHost::get(jsi::Runtime& rt, const jsi::PropNameID& name) {
|
|
|
281
289
|
// callback into a backend failure. The nodejs SDK is WASM and
|
|
282
290
|
// single-threaded, so the hazard cannot arise there. Queuing onto the
|
|
283
291
|
// serial worker instead orders the setter behind any in-flight flow, and
|
|
284
|
-
//
|
|
285
|
-
// nodejs code calls these — source-compatible, while letting a failure
|
|
286
|
-
// surface as a rejection rather than being swallowed.
|
|
292
|
+
// a failure surfaces as a rejection rather than being swallowed.
|
|
287
293
|
if (prop == "setCommunicationInfo") {
|
|
288
294
|
return jsi::Function::createFromHostFunction(
|
|
289
295
|
rt, name, 1,
|
|
@@ -309,38 +315,6 @@ jsi::Value ProtocolHost::get(jsi::Runtime& rt, const jsi::PropNameID& name) {
|
|
|
309
315
|
});
|
|
310
316
|
}
|
|
311
317
|
|
|
312
|
-
if (prop == "setOwnTransport") {
|
|
313
|
-
return jsi::Function::createFromHostFunction(
|
|
314
|
-
rt, name, 2,
|
|
315
|
-
[this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value* args,
|
|
316
|
-
size_t count) -> jsi::Value {
|
|
317
|
-
requireArgs(rt2, "setOwnTransport", count, 2);
|
|
318
|
-
std::string uri = args[0].asString(rt2).utf8(rt2);
|
|
319
|
-
auto protocol = static_cast<int32_t>(args[1].asNumber());
|
|
320
|
-
DeRecProtocolHandle* handle = handle_;
|
|
321
|
-
|
|
322
|
-
auto body = [handle, uri, protocol]() -> std::vector<uint8_t> {
|
|
323
|
-
// This binding *is* the deprecated single-endpoint path, so it calls the
|
|
324
|
-
// deprecated export on purpose — the same reason the Rust side carries
|
|
325
|
-
// `#[allow(deprecated)]` on its compatibility paths. Both go at 0.0.5.
|
|
326
|
-
#pragma GCC diagnostic push
|
|
327
|
-
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
328
|
-
DeRecError error = derec_protocol_set_own_transport(
|
|
329
|
-
handle, reinterpret_cast<const uint8_t*>(uri.data()), uri.size(),
|
|
330
|
-
protocol);
|
|
331
|
-
#pragma GCC diagnostic pop
|
|
332
|
-
if (error.code != DEREC_CODE_OK) {
|
|
333
|
-
throw FfiFailure(error);
|
|
334
|
-
}
|
|
335
|
-
return {};
|
|
336
|
-
};
|
|
337
|
-
auto convert = [](jsi::Runtime&, std::vector<uint8_t>&) {
|
|
338
|
-
return jsi::Value::undefined();
|
|
339
|
-
};
|
|
340
|
-
return runAsync(rt2, std::move(body), std::move(convert));
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
|
|
344
318
|
// Takes the endpoint list already JSON-encoded, the same way
|
|
345
319
|
// `setCommunicationInfo` takes its map: the FFI parses JSON on the other
|
|
346
320
|
// side, so encoding in JavaScript keeps this bridge free of a shape it
|
|
@@ -599,12 +573,14 @@ jsi::Value ProtocolHost::get(jsi::Runtime& rt, const jsi::PropNameID& name) {
|
|
|
599
573
|
|
|
600
574
|
auto body = [handle,
|
|
601
575
|
params = std::move(params)]() -> std::vector<uint8_t> {
|
|
602
|
-
|
|
576
|
+
DeRecProtocolRestoreResult result =
|
|
603
577
|
derec_protocol_restore(handle, params.data(), params.size());
|
|
604
|
-
|
|
605
|
-
|
|
578
|
+
RestoreOutcome outcome = takeRestoreResult(result);
|
|
579
|
+
if (outcome.error.code != DEREC_CODE_OK) {
|
|
580
|
+
throw FfiFailure(outcome.error,
|
|
581
|
+
std::move(outcome.conflictingChannelIdsJson));
|
|
606
582
|
}
|
|
607
|
-
return
|
|
583
|
+
return std::move(outcome.events);
|
|
608
584
|
};
|
|
609
585
|
return runAsync(rt2, std::move(body), bytesToArrayBuffer);
|
|
610
586
|
});
|
|
@@ -632,8 +608,7 @@ jsi::Value ProtocolHost::get(jsi::Runtime& rt, const jsi::PropNameID& name) {
|
|
|
632
608
|
|
|
633
609
|
std::vector<jsi::PropNameID> ProtocolHost::getPropertyNames(jsi::Runtime& rt) {
|
|
634
610
|
const char* names[] = {
|
|
635
|
-
"secretId", "setCommunicationInfo", "
|
|
636
|
-
"setOwnTransports",
|
|
611
|
+
"secretId", "setCommunicationInfo", "setOwnTransports",
|
|
637
612
|
"createContact", "start", "process",
|
|
638
613
|
"tick", "accept", "reject",
|
|
639
614
|
"getFingerprint", "verifyFingerprint", "removeExpiredChannels",
|
package/cpp/StoreCallbacks.cpp
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
#include "Convert.h"
|
|
12
12
|
#include "Primitives.h"
|
|
13
|
+
#include "UserSecretsJson.h"
|
|
13
14
|
|
|
14
15
|
using namespace facebook;
|
|
15
16
|
|
|
@@ -24,7 +25,7 @@ namespace {
|
|
|
24
25
|
|
|
25
26
|
static_assert(sizeof(ChannelStoreCallbacks) == 9 * sizeof(void*),
|
|
26
27
|
"ChannelStoreCallbacks layout changed; update the bindings");
|
|
27
|
-
static_assert(sizeof(SecretStoreCallbacks) ==
|
|
28
|
+
static_assert(sizeof(SecretStoreCallbacks) == 6 * sizeof(void*),
|
|
28
29
|
"SecretStoreCallbacks layout changed; update the bindings");
|
|
29
30
|
static_assert(sizeof(ShareStoreCallbacks) == 8 * sizeof(void*),
|
|
30
31
|
"ShareStoreCallbacks layout changed; update the bindings");
|
|
@@ -74,118 +75,6 @@ jsi::Value idVal(jsi::Runtime& rt, uint64_t id) {
|
|
|
74
75
|
return jsi::Value(rt, jsi::String::createFromUtf8(rt, std::to_string(id)));
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
/// Build a genuine `Uint8Array` (not a bare `ArrayBuffer`) — every store
|
|
78
|
-
/// interface types its byte payloads as `Uint8Array`, and application code
|
|
79
|
-
/// is entitled to index or slice it directly.
|
|
80
|
-
jsi::Value toUint8ArrayVal(jsi::Runtime& rt, const uint8_t* bytes, size_t len) {
|
|
81
|
-
jsi::Uint8Array arr(rt, len);
|
|
82
|
-
if (len > 0 && bytes != nullptr) {
|
|
83
|
-
jsi::ArrayBuffer buffer = arr.buffer(rt);
|
|
84
|
-
std::memcpy(buffer.data(rt), bytes, len);
|
|
85
|
-
}
|
|
86
|
-
return jsi::Value(rt, std::move(arr));
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
jsi::Value toUint8ArrayVal(jsi::Runtime& rt, const std::vector<uint8_t>& bytes) {
|
|
90
|
-
return toUint8ArrayVal(rt, bytes.data(), bytes.size());
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Minimal decoders for the wire's "bare JSON array/object of unsigned
|
|
94
|
-
// integers" convention (`versions_json`, `channel_ids_json`, the `bytes`
|
|
95
|
-
// field of a `ShareRecord` / `SecretValueRecord`). These are produced by
|
|
96
|
-
// `serde_json`'s default compact, whitespace-free output over a fixed,
|
|
97
|
-
// known shape, so a substring scan is sufficient and — critically — never
|
|
98
|
-
// routes a `u64` through a JS `number`, which cannot represent every value
|
|
99
|
-
// in that range exactly.
|
|
100
|
-
|
|
101
|
-
/// Parse a bare `[n, n, ...]` array of unsigned decimal integers.
|
|
102
|
-
std::vector<uint64_t> parseUnsignedJsonArray(const uint8_t* ptr, size_t len) {
|
|
103
|
-
std::vector<uint64_t> out;
|
|
104
|
-
size_t i = 0;
|
|
105
|
-
while (i < len && ptr[i] != '[') ++i;
|
|
106
|
-
if (i >= len) return out;
|
|
107
|
-
++i;
|
|
108
|
-
while (i < len && ptr[i] != ']') {
|
|
109
|
-
if (ptr[i] < '0' || ptr[i] > '9') {
|
|
110
|
-
++i;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
uint64_t value = 0;
|
|
114
|
-
while (i < len && ptr[i] >= '0' && ptr[i] <= '9') {
|
|
115
|
-
value = value * 10 + static_cast<uint64_t>(ptr[i] - '0');
|
|
116
|
-
++i;
|
|
117
|
-
}
|
|
118
|
-
out.push_back(value);
|
|
119
|
-
}
|
|
120
|
-
return out;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/// Offset just past `"key":` in a flat, compact JSON object, or
|
|
124
|
-
/// `std::string::npos` if `key` is absent.
|
|
125
|
-
size_t fieldValueOffset(const std::string& json, const char* key) {
|
|
126
|
-
std::string needle = std::string("\"") + key + "\":";
|
|
127
|
-
size_t pos = json.find(needle);
|
|
128
|
-
return pos == std::string::npos ? std::string::npos : pos + needle.size();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
uint64_t parseUnsignedAt(const std::string& json, size_t pos) {
|
|
132
|
-
uint64_t value = 0;
|
|
133
|
-
while (pos < json.size() && json[pos] >= '0' && json[pos] <= '9') {
|
|
134
|
-
value = value * 10 + static_cast<uint64_t>(json[pos] - '0');
|
|
135
|
-
++pos;
|
|
136
|
-
}
|
|
137
|
-
return value;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
std::vector<uint8_t> parseByteArrayAt(const std::string& json, size_t pos) {
|
|
141
|
-
std::vector<uint8_t> out;
|
|
142
|
-
while (pos < json.size() && json[pos] != '[') ++pos;
|
|
143
|
-
if (pos >= json.size()) return out;
|
|
144
|
-
++pos;
|
|
145
|
-
while (pos < json.size() && json[pos] != ']') {
|
|
146
|
-
if (json[pos] < '0' || json[pos] > '9') {
|
|
147
|
-
++pos;
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
uint64_t value = 0;
|
|
151
|
-
while (pos < json.size() && json[pos] >= '0' && json[pos] <= '9') {
|
|
152
|
-
value = value * 10 + static_cast<uint64_t>(json[pos] - '0');
|
|
153
|
-
++pos;
|
|
154
|
-
}
|
|
155
|
-
out.push_back(static_cast<uint8_t>(value));
|
|
156
|
-
}
|
|
157
|
-
return out;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/// `{"kind":<n>,"bytes":[<n>,...]}` — the wire shape `SecretStore.save`
|
|
161
|
-
/// receives; `kind` is already carried as a separate FFI argument so only
|
|
162
|
-
/// `bytes` needs to be read back out.
|
|
163
|
-
std::vector<uint8_t> decodeSecretValueBytes(const uint8_t* ptr, size_t len) {
|
|
164
|
-
std::string json(reinterpret_cast<const char*>(ptr), len);
|
|
165
|
-
size_t pos = fieldValueOffset(json, "bytes");
|
|
166
|
-
if (pos == std::string::npos) return {};
|
|
167
|
-
return parseByteArrayAt(json, pos);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/// `{"secret_id":"...","version":<n>,"bytes":[<n>,...]}` — the wire shape
|
|
171
|
-
/// `ShareStore.save` receives. `secret_id` duplicates the FFI's own
|
|
172
|
-
/// `secret_id` argument, so only `version` and `bytes` are decoded.
|
|
173
|
-
struct DecodedShare {
|
|
174
|
-
uint32_t version;
|
|
175
|
-
std::vector<uint8_t> bytes;
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
DecodedShare decodeShareRecord(const uint8_t* ptr, size_t len) {
|
|
179
|
-
std::string json(reinterpret_cast<const char*>(ptr), len);
|
|
180
|
-
size_t versionPos = fieldValueOffset(json, "version");
|
|
181
|
-
size_t bytesPos = fieldValueOffset(json, "bytes");
|
|
182
|
-
DecodedShare out{};
|
|
183
|
-
out.version =
|
|
184
|
-
versionPos == std::string::npos ? 0 : static_cast<uint32_t>(parseUnsignedAt(json, versionPos));
|
|
185
|
-
out.bytes = bytesPos == std::string::npos ? std::vector<uint8_t>{} : parseByteArrayAt(json, bytesPos);
|
|
186
|
-
return out;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
78
|
/// True only for a non-empty run of ASCII decimal digits.
|
|
190
79
|
///
|
|
191
80
|
/// Every `u64` id crosses this boundary as a decimal string, and the wire
|
|
@@ -376,6 +265,37 @@ ResultConverter makeSecretLoadResult(uint32_t kind) {
|
|
|
376
265
|
};
|
|
377
266
|
}
|
|
378
267
|
|
|
268
|
+
/// `SecretStore.loadMany(): Promise<Array<Uint8Array | null | undefined>>`.
|
|
269
|
+
/// Encodes the resolved array element by element, in order, as the wire's
|
|
270
|
+
/// `Vec<Option<SecretValueRecord>>` JSON: the record `load` builds, or `null`
|
|
271
|
+
/// for a `null`/`undefined` entry. The library pairs entries with the
|
|
272
|
+
/// requested ids and decides what a missing one means.
|
|
273
|
+
///
|
|
274
|
+
/// A non-array return is a backend failure, never an empty list.
|
|
275
|
+
ResultConverter makeSecretLoadManyResult(uint32_t kind) {
|
|
276
|
+
return [kind](jsi::Runtime& rt, const jsi::Value& value) -> CallResult {
|
|
277
|
+
if (!value.isObject()) {
|
|
278
|
+
return CallResult{kBackendFailure, {}};
|
|
279
|
+
}
|
|
280
|
+
jsi::Array arr = value.asObject(rt).asArray(rt);
|
|
281
|
+
size_t n = arr.size(rt);
|
|
282
|
+
std::string text = "[";
|
|
283
|
+
for (size_t i = 0; i < n; ++i) {
|
|
284
|
+
if (i != 0) text += ',';
|
|
285
|
+
jsi::Value entry = arr.getValueAtIndex(rt, i);
|
|
286
|
+
if (entry.isNull() || entry.isUndefined()) {
|
|
287
|
+
text += "null";
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
ByteView bytes = asBytes(rt, entry);
|
|
291
|
+
std::vector<uint8_t> record = secretValueRecordJson(kind, bytes.ptr, bytes.len);
|
|
292
|
+
text.append(record.begin(), record.end());
|
|
293
|
+
}
|
|
294
|
+
text += ']';
|
|
295
|
+
return CallResult{0, std::vector<uint8_t>(text.begin(), text.end())};
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
379
299
|
/// `StateStore.loadAll(): Promise<Uint8Array[]>`. Each element is already
|
|
380
300
|
/// the exact item-JSON blob the wire expects; splice the raw bytes into an
|
|
381
301
|
/// array rather than re-parsing and re-serialising them.
|
|
@@ -400,105 +320,12 @@ CallResult toStateLoadAllResult(jsi::Runtime& rt, const jsi::Value& value) {
|
|
|
400
320
|
return CallResult{0, std::vector<uint8_t>(text.begin(), text.end())};
|
|
401
321
|
}
|
|
402
322
|
|
|
403
|
-
// `UserSecrets` JSON <-> JS object conversion. Unlike the flat integer-only
|
|
404
|
-
// records above, `UserSecrets` carries application-controlled `name` /
|
|
405
|
-
// `description` strings, so building or reading its wire JSON goes through
|
|
406
|
-
// the runtime's own `JSON.parse` / `JSON.stringify` rather than a hand-rolled
|
|
407
|
-
// scanner — the only place in this file that needs real string escaping.
|
|
408
|
-
|
|
409
|
-
jsi::Value jsonParseUtf8(jsi::Runtime& rt, const uint8_t* bytes, size_t len) {
|
|
410
|
-
jsi::Object json = rt.global().getPropertyAsObject(rt, "JSON");
|
|
411
|
-
jsi::Function parse = json.getPropertyAsFunction(rt, "parse");
|
|
412
|
-
std::string text(reinterpret_cast<const char*>(bytes), len);
|
|
413
|
-
return parse.call(rt, jsi::Value(rt, jsi::String::createFromUtf8(rt, text)));
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
std::vector<uint8_t> jsonStringifyToUtf8(jsi::Runtime& rt, const jsi::Value& value) {
|
|
417
|
-
jsi::Object json = rt.global().getPropertyAsObject(rt, "JSON");
|
|
418
|
-
jsi::Function stringify = json.getPropertyAsFunction(rt, "stringify");
|
|
419
|
-
jsi::Value result = stringify.call(rt, value);
|
|
420
|
-
std::string text = result.asString(rt).utf8(rt);
|
|
421
|
-
return std::vector<uint8_t>(text.begin(), text.end());
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
jsi::Array bytesToNumberArray(jsi::Runtime& rt, ByteView view) {
|
|
425
|
-
jsi::Array arr(rt, view.len);
|
|
426
|
-
for (size_t i = 0; i < view.len; ++i) {
|
|
427
|
-
arr.setValueAtIndex(rt, i, jsi::Value(static_cast<int>(view.ptr[i])));
|
|
428
|
-
}
|
|
429
|
-
return arr;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
std::vector<uint8_t> numberArrayToBytes(jsi::Runtime& rt, jsi::Array arr) {
|
|
433
|
-
size_t n = arr.size(rt);
|
|
434
|
-
std::vector<uint8_t> out;
|
|
435
|
-
out.reserve(n);
|
|
436
|
-
for (size_t i = 0; i < n; ++i) {
|
|
437
|
-
out.push_back(static_cast<uint8_t>(arr.getValueAtIndex(rt, i).asNumber()));
|
|
438
|
-
}
|
|
439
|
-
return out;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
323
|
/// `UserSecretStore.loadLatest(): Promise<UserSecrets | null | undefined>`.
|
|
443
324
|
CallResult toUserSecretsResult(jsi::Runtime& rt, const jsi::Value& value) {
|
|
444
325
|
if (value.isNull() || value.isUndefined()) {
|
|
445
326
|
return CallResult{1, {}};
|
|
446
327
|
}
|
|
447
|
-
|
|
448
|
-
jsi::Object wire(rt);
|
|
449
|
-
wire.setProperty(rt, "version", jsi::Value(rt, src.getProperty(rt, "version")));
|
|
450
|
-
jsi::Array srcSecrets = src.getProperty(rt, "secrets").asObject(rt).asArray(rt);
|
|
451
|
-
size_t n = srcSecrets.size(rt);
|
|
452
|
-
jsi::Array wireSecrets(rt, n);
|
|
453
|
-
for (size_t i = 0; i < n; ++i) {
|
|
454
|
-
jsi::Object entry = srcSecrets.getValueAtIndex(rt, i).asObject(rt);
|
|
455
|
-
jsi::Object wireEntry(rt);
|
|
456
|
-
wireEntry.setProperty(rt, "id", jsi::Value(rt, bytesToNumberArray(rt, asBytes(rt, entry.getProperty(rt, "id")))));
|
|
457
|
-
wireEntry.setProperty(rt, "name", jsi::Value(rt, entry.getProperty(rt, "name")));
|
|
458
|
-
wireEntry.setProperty(
|
|
459
|
-
rt, "data", jsi::Value(rt, bytesToNumberArray(rt, asBytes(rt, entry.getProperty(rt, "data")))));
|
|
460
|
-
wireSecrets.setValueAtIndex(rt, i, jsi::Value(rt, wireEntry));
|
|
461
|
-
}
|
|
462
|
-
wire.setProperty(rt, "secrets", jsi::Value(rt, wireSecrets));
|
|
463
|
-
if (src.hasProperty(rt, "description")) {
|
|
464
|
-
jsi::Value description = src.getProperty(rt, "description");
|
|
465
|
-
if (!description.isUndefined()) {
|
|
466
|
-
wire.setProperty(rt, "description", description);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
return CallResult{0, jsonStringifyToUtf8(rt, jsi::Value(rt, wire))};
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/// Decode `UserSecretStore.saveLatest`'s incoming wire JSON into the JS
|
|
473
|
-
/// `UserSecrets` shape `src/types.ts` declares.
|
|
474
|
-
jsi::Value buildUserSecretsFromWire(jsi::Runtime& rt, const uint8_t* ptr, size_t len) {
|
|
475
|
-
jsi::Value parsed = jsonParseUtf8(rt, ptr, len);
|
|
476
|
-
jsi::Object src = parsed.asObject(rt);
|
|
477
|
-
jsi::Object out(rt);
|
|
478
|
-
out.setProperty(rt, "version", jsi::Value(rt, src.getProperty(rt, "version")));
|
|
479
|
-
jsi::Array srcSecrets = src.getProperty(rt, "secrets").asObject(rt).asArray(rt);
|
|
480
|
-
size_t n = srcSecrets.size(rt);
|
|
481
|
-
jsi::Array outSecrets(rt, n);
|
|
482
|
-
for (size_t i = 0; i < n; ++i) {
|
|
483
|
-
jsi::Object entry = srcSecrets.getValueAtIndex(rt, i).asObject(rt);
|
|
484
|
-
jsi::Object outEntry(rt);
|
|
485
|
-
std::vector<uint8_t> idBytes =
|
|
486
|
-
numberArrayToBytes(rt, entry.getProperty(rt, "id").asObject(rt).asArray(rt));
|
|
487
|
-
std::vector<uint8_t> dataBytes =
|
|
488
|
-
numberArrayToBytes(rt, entry.getProperty(rt, "data").asObject(rt).asArray(rt));
|
|
489
|
-
outEntry.setProperty(rt, "id", toUint8ArrayVal(rt, idBytes));
|
|
490
|
-
outEntry.setProperty(rt, "name", jsi::Value(rt, entry.getProperty(rt, "name")));
|
|
491
|
-
outEntry.setProperty(rt, "data", toUint8ArrayVal(rt, dataBytes));
|
|
492
|
-
outSecrets.setValueAtIndex(rt, i, jsi::Value(rt, outEntry));
|
|
493
|
-
}
|
|
494
|
-
out.setProperty(rt, "secrets", jsi::Value(rt, outSecrets));
|
|
495
|
-
if (src.hasProperty(rt, "description")) {
|
|
496
|
-
jsi::Value description = src.getProperty(rt, "description");
|
|
497
|
-
if (!description.isUndefined()) {
|
|
498
|
-
out.setProperty(rt, "description", description);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
return jsi::Value(rt, out);
|
|
328
|
+
return CallResult{0, userSecretsToWire(rt, value.asObject(rt))};
|
|
502
329
|
}
|
|
503
330
|
|
|
504
331
|
/// Releases a buffer this binding handed to Rust through an out-parameter.
|
|
@@ -654,7 +481,7 @@ extern "C" int32_t channelStoreListReplicas(void* userData, uint64_t secretId, c
|
|
|
654
481
|
return result.code;
|
|
655
482
|
}
|
|
656
483
|
|
|
657
|
-
/// `ChannelStore.linkChannel(secretId,
|
|
484
|
+
/// `ChannelStore.linkChannel(secretId, a, b) -> void`
|
|
658
485
|
extern "C" int32_t channelStoreLinkChannel(void* userData, uint64_t secretId, uint64_t a, uint64_t b) {
|
|
659
486
|
auto* self = static_cast<StoreBindings*>(userData);
|
|
660
487
|
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
@@ -728,6 +555,39 @@ extern "C" int32_t secretStoreLoad(void* userData, uint64_t secretId, uint64_t c
|
|
|
728
555
|
return result.code;
|
|
729
556
|
}
|
|
730
557
|
|
|
558
|
+
/// `SecretStore.loadMany(secretId, channelIds, kind) -> Array<Uint8Array | null>`
|
|
559
|
+
extern "C" int32_t secretStoreLoadMany(void* userData, uint64_t secretId, const uint8_t* channelIdsJsonPtr,
|
|
560
|
+
size_t channelIdsJsonLen, uint32_t kind, uint8_t** outPtr,
|
|
561
|
+
size_t* outLen) {
|
|
562
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
563
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
564
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
565
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
566
|
+
auto keepAlive = self->shared_from_this();
|
|
567
|
+
std::vector<uint64_t> channelIds = parseUnsignedJsonArray(channelIdsJsonPtr, channelIdsJsonLen);
|
|
568
|
+
CallResult result = keepAlive->bridge().callSync(
|
|
569
|
+
[keepAlive, secretId, channelIds, kind](std::function<void(CallResult)> settle) {
|
|
570
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
571
|
+
auto store = keepAlive->secretStore();
|
|
572
|
+
auto method = store->getPropertyAsFunction(rt, "loadMany");
|
|
573
|
+
jsi::Value promise =
|
|
574
|
+
method.callWithThis(rt, *store, idVal(rt, secretId),
|
|
575
|
+
jsi::Value(rt, u64VectorToJsStringArray(rt, channelIds)),
|
|
576
|
+
jsi::Value(static_cast<int>(kind)));
|
|
577
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle),
|
|
578
|
+
makeSecretLoadManyResult(kind));
|
|
579
|
+
});
|
|
580
|
+
if (result.code == 0) {
|
|
581
|
+
if (!writeOut(result.bytes, outPtr, outLen)) {
|
|
582
|
+
return kBackendFailure;
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
*outPtr = nullptr;
|
|
586
|
+
*outLen = 0;
|
|
587
|
+
}
|
|
588
|
+
return result.code;
|
|
589
|
+
}
|
|
590
|
+
|
|
731
591
|
/// `SecretStore.save(secretId, channelId, kind, value) -> void`
|
|
732
592
|
extern "C" int32_t secretStoreSave(void* userData, uint64_t secretId, uint64_t channelId, uint32_t kind,
|
|
733
593
|
const uint8_t* bytes, size_t len) {
|
|
@@ -899,14 +759,22 @@ extern "C" int32_t shareStoreSave(void* userData, uint64_t secretId, uint64_t ch
|
|
|
899
759
|
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
900
760
|
// owner has released its own reference — has somewhere safe to run.
|
|
901
761
|
auto keepAlive = self->shared_from_this();
|
|
902
|
-
DecodedShare
|
|
762
|
+
std::optional<DecodedShare> parsed = decodeShareRecord(shareJsonPtr, shareJsonLen);
|
|
763
|
+
if (!parsed) {
|
|
764
|
+
return kBackendFailure;
|
|
765
|
+
}
|
|
766
|
+
// `share.secretId` is the record's own `secret_id`, not the partition
|
|
767
|
+
// `secretId`: on a helper the share carries the owner's secret id, which is
|
|
768
|
+
// what tells apart several secrets held in one partition.
|
|
769
|
+
DecodedShare decoded = std::move(*parsed);
|
|
903
770
|
CallResult result = keepAlive->bridge().callSync([keepAlive, secretId, channelId,
|
|
904
771
|
decoded](std::function<void(CallResult)> settle) {
|
|
905
772
|
jsi::Runtime& rt = keepAlive->runtime();
|
|
906
773
|
auto store = keepAlive->shareStore();
|
|
907
774
|
auto method = store->getPropertyAsFunction(rt, "save");
|
|
908
775
|
jsi::Object shareObj(rt);
|
|
909
|
-
shareObj.setProperty(rt, "secretId",
|
|
776
|
+
shareObj.setProperty(rt, "secretId",
|
|
777
|
+
jsi::Value(rt, jsi::String::createFromUtf8(rt, decoded.secretId)));
|
|
910
778
|
shareObj.setProperty(rt, "version", jsi::Value(static_cast<int>(decoded.version)));
|
|
911
779
|
shareObj.setProperty(rt, "bytes", toUint8ArrayVal(rt, decoded.bytes));
|
|
912
780
|
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId), idVal(rt, channelId),
|
|
@@ -973,7 +841,7 @@ extern "C" int32_t userSecretStoreSaveLatest(void* userData, uint64_t secretId,
|
|
|
973
841
|
jsi::Runtime& rt = keepAlive->runtime();
|
|
974
842
|
auto store = keepAlive->userSecretStore();
|
|
975
843
|
auto method = store->getPropertyAsFunction(rt, "saveLatest");
|
|
976
|
-
jsi::Value userSecrets =
|
|
844
|
+
jsi::Value userSecrets = userSecretsFromWire(rt, payload.data(), payload.size());
|
|
977
845
|
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId), std::move(userSecrets));
|
|
978
846
|
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toVoidResult);
|
|
979
847
|
});
|
|
@@ -1092,64 +960,6 @@ extern "C" int32_t stateStoreLoadAll(void* userData, uint64_t secretId, uint32_t
|
|
|
1092
960
|
return result.code;
|
|
1093
961
|
}
|
|
1094
962
|
|
|
1095
|
-
/// Maps the wire's `derec_proto::Protocol` discriminant to the string
|
|
1096
|
-
/// `Transport.send`'s endpoint carries. Only `HTTPS = 0` is defined today;
|
|
1097
|
-
/// the protocol is explicitly extensible, so an unrecognised discriminant
|
|
1098
|
-
/// is reported rather than guessed at.
|
|
1099
|
-
std::string protocolToString(int32_t protocol) { return protocol == 0 ? "https" : "unknown"; }
|
|
1100
|
-
|
|
1101
|
-
/// Minimal reader for an encoded `TransportProtocol`: field 1 is the URI
|
|
1102
|
-
/// (length-delimited string), field 2 the protocol discriminant (varint).
|
|
1103
|
-
/// Hand-rolled rather than linking a protobuf runtime into the JSI layer for
|
|
1104
|
-
/// two fields; unknown fields are skipped so a newer library stays readable.
|
|
1105
|
-
static std::optional<std::pair<std::string, int32_t>> decodeTransportProtocol(
|
|
1106
|
-
const uint8_t* data, size_t len) {
|
|
1107
|
-
std::string uri;
|
|
1108
|
-
int32_t protocol = 0;
|
|
1109
|
-
size_t i = 0;
|
|
1110
|
-
|
|
1111
|
-
auto readVarint = [&](uint64_t& out) -> bool {
|
|
1112
|
-
out = 0;
|
|
1113
|
-
int shift = 0;
|
|
1114
|
-
while (i < len) {
|
|
1115
|
-
uint8_t b = data[i++];
|
|
1116
|
-
out |= static_cast<uint64_t>(b & 0x7F) << shift;
|
|
1117
|
-
if ((b & 0x80) == 0) return true;
|
|
1118
|
-
shift += 7;
|
|
1119
|
-
if (shift > 63) return false;
|
|
1120
|
-
}
|
|
1121
|
-
return false;
|
|
1122
|
-
};
|
|
1123
|
-
|
|
1124
|
-
while (i < len) {
|
|
1125
|
-
uint64_t tag = 0;
|
|
1126
|
-
if (!readVarint(tag)) return std::nullopt;
|
|
1127
|
-
uint32_t field = static_cast<uint32_t>(tag >> 3);
|
|
1128
|
-
uint32_t wire = static_cast<uint32_t>(tag & 0x7);
|
|
1129
|
-
|
|
1130
|
-
if (field == 1 && wire == 2) {
|
|
1131
|
-
uint64_t size = 0;
|
|
1132
|
-
if (!readVarint(size) || i + size > len) return std::nullopt;
|
|
1133
|
-
uri.assign(reinterpret_cast<const char*>(data + i), size);
|
|
1134
|
-
i += size;
|
|
1135
|
-
} else if (field == 2 && wire == 0) {
|
|
1136
|
-
uint64_t value = 0;
|
|
1137
|
-
if (!readVarint(value)) return std::nullopt;
|
|
1138
|
-
protocol = static_cast<int32_t>(value);
|
|
1139
|
-
} else if (wire == 0) {
|
|
1140
|
-
uint64_t skip = 0;
|
|
1141
|
-
if (!readVarint(skip)) return std::nullopt;
|
|
1142
|
-
} else if (wire == 2) {
|
|
1143
|
-
uint64_t size = 0;
|
|
1144
|
-
if (!readVarint(size) || i + size > len) return std::nullopt;
|
|
1145
|
-
i += size;
|
|
1146
|
-
} else {
|
|
1147
|
-
return std::nullopt;
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
return std::make_pair(uri, protocol);
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
963
|
/// `Transport.send(endpoints, message) -> void`
|
|
1154
964
|
///
|
|
1155
965
|
/// `endpoints` is every address the peer advertised, in the peer's order,
|
|
@@ -1164,44 +974,25 @@ extern "C" int32_t transportSend(void* userData, const uint8_t* endpointsPtr, si
|
|
|
1164
974
|
// owner has released its own reference — has somewhere safe to run.
|
|
1165
975
|
auto keepAlive = self->shared_from_this();
|
|
1166
976
|
|
|
1167
|
-
//
|
|
1168
|
-
//
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
while (offset < endpointsLen) {
|
|
1175
|
-
uint8_t b = endpointsPtr[offset++];
|
|
1176
|
-
size |= static_cast<uint64_t>(b & 0x7F) << shift;
|
|
1177
|
-
if ((b & 0x80) == 0) break;
|
|
1178
|
-
shift += 7;
|
|
1179
|
-
}
|
|
1180
|
-
if (offset + size > endpointsLen) return -1;
|
|
1181
|
-
auto parsed = decodeTransportProtocol(endpointsPtr + offset, size);
|
|
1182
|
-
if (!parsed) return -1;
|
|
1183
|
-
endpoints.push_back(*parsed);
|
|
1184
|
-
offset += size;
|
|
977
|
+
// The crate decodes its own endpoint framing, names each protocol, and
|
|
978
|
+
// fails the send on a discriminant it cannot name; this layer only parses
|
|
979
|
+
// the JSON array `Transport.send` receives.
|
|
980
|
+
DeRecMessageJsonResult decoded = derec_transport_endpoints_json(endpointsPtr, endpointsLen);
|
|
981
|
+
if (decoded.error.code != 0) {
|
|
982
|
+
derec_free_error(&decoded.error);
|
|
983
|
+
return -1;
|
|
1185
984
|
}
|
|
985
|
+
std::vector<uint8_t> endpointsJson = takeBuffer(decoded.bytes);
|
|
1186
986
|
|
|
1187
987
|
std::vector<uint8_t> message(bytes, bytes + len);
|
|
1188
|
-
CallResult result = keepAlive->bridge().callSync([keepAlive,
|
|
988
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, endpointsJson,
|
|
1189
989
|
message](std::function<void(CallResult)> settle) {
|
|
1190
990
|
jsi::Runtime& rt = keepAlive->runtime();
|
|
1191
991
|
auto store = keepAlive->transportStore();
|
|
1192
992
|
auto method = store->getPropertyAsFunction(rt, "send");
|
|
1193
|
-
jsi::
|
|
1194
|
-
for (size_t i = 0; i < endpoints.size(); ++i) {
|
|
1195
|
-
jsi::Object endpoint(rt);
|
|
1196
|
-
endpoint.setProperty(rt, "protocol",
|
|
1197
|
-
jsi::Value(rt, jsi::String::createFromUtf8(
|
|
1198
|
-
rt, protocolToString(endpoints[i].second))));
|
|
1199
|
-
endpoint.setProperty(rt, "uri",
|
|
1200
|
-
jsi::Value(rt, jsi::String::createFromUtf8(rt, endpoints[i].first)));
|
|
1201
|
-
endpointsJs.setValueAtIndex(rt, i, endpoint);
|
|
1202
|
-
}
|
|
993
|
+
jsi::Value endpointsJs = jsonParseUtf8(rt, endpointsJson.data(), endpointsJson.size());
|
|
1203
994
|
jsi::Value promise =
|
|
1204
|
-
method.callWithThis(rt, *store,
|
|
995
|
+
method.callWithThis(rt, *store, endpointsJs, toUint8ArrayVal(rt, message));
|
|
1205
996
|
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toVoidResult);
|
|
1206
997
|
});
|
|
1207
998
|
return result.code;
|
|
@@ -1318,11 +1109,12 @@ std::shared_ptr<StoreBindings> StoreBindings::create(jsi::Runtime& rt, jsi::Obje
|
|
|
1318
1109
|
storeFreeBuffer, // free_buffer
|
|
1319
1110
|
};
|
|
1320
1111
|
bindings->secretCallbacks_ = SecretStoreCallbacks{
|
|
1321
|
-
self,
|
|
1322
|
-
secretStoreLoad,
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1112
|
+
self, // user_data
|
|
1113
|
+
secretStoreLoad, // load
|
|
1114
|
+
secretStoreLoadMany, // load_many
|
|
1115
|
+
secretStoreSave, // save
|
|
1116
|
+
secretStoreRemove, // remove
|
|
1117
|
+
storeFreeBuffer, // free_buffer
|
|
1326
1118
|
};
|
|
1327
1119
|
bindings->shareCallbacks_ = ShareStoreCallbacks{
|
|
1328
1120
|
self, // user_data
|
|
@@ -1366,6 +1158,7 @@ std::shared_ptr<StoreBindings> StoreBindings::create(jsi::Runtime& rt, jsi::Obje
|
|
|
1366
1158
|
|
|
1367
1159
|
requireField(rt, bindings->secretCallbacks_.user_data, "SecretStoreCallbacks.user_data");
|
|
1368
1160
|
requireField(rt, bindings->secretCallbacks_.load, "SecretStoreCallbacks.load");
|
|
1161
|
+
requireField(rt, bindings->secretCallbacks_.load_many, "SecretStoreCallbacks.load_many");
|
|
1369
1162
|
requireField(rt, bindings->secretCallbacks_.save, "SecretStoreCallbacks.save");
|
|
1370
1163
|
requireField(rt, bindings->secretCallbacks_.remove, "SecretStoreCallbacks.remove");
|
|
1371
1164
|
requireField(rt, bindings->secretCallbacks_.free_buffer, "SecretStoreCallbacks.free_buffer");
|