@derec-alliance/react-native 0.0.2 → 0.0.4
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 +46 -2
- 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/Primitives.cpp +27 -18
- package/cpp/ProtocolHost.cpp +37 -0
- package/cpp/StoreCallbacks.cpp +127 -19
- package/cpp/derec_ffi.h +145 -24
- package/ios/DeRecFFI.xcframework/ios-arm64/Headers/derec_ffi.h +145 -24
- package/ios/DeRecFFI.xcframework/ios-arm64/libderec_library.a +0 -0
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/Headers/derec_ffi.h +145 -24
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/libderec_library.a +2 -2
- package/lib/primitives.d.ts +9 -9
- package/lib/primitives.js +63 -13
- package/lib/protocol.d.ts +85 -6
- package/lib/protocol.js +119 -8
- package/lib/types.d.ts +313 -29
- package/lib/types.js +109 -2
- package/package.json +2 -1
- package/src/primitives.ts +77 -21
- package/src/protocol.ts +138 -13
- package/src/types.ts +379 -35
package/README.md
CHANGED
|
@@ -58,6 +58,24 @@ release build already includes whatever native modules are linked in.
|
|
|
58
58
|
|
|
59
59
|
---
|
|
60
60
|
|
|
61
|
+
## Protocol schema
|
|
62
|
+
|
|
63
|
+
The package ships the DeRec `.proto` schema under `schema/` so you can
|
|
64
|
+
generate code for any language without a `lib-derec` checkout:
|
|
65
|
+
|
|
66
|
+
- `schema/proto/` — the 18 schema files, flat. Every import is a bare
|
|
67
|
+
filename, so a single include root resolves the whole closure:
|
|
68
|
+
`protoc --proto_path=node_modules/@derec-alliance/react-native/schema/proto node_modules/@derec-alliance/react-native/schema/proto/*.proto`
|
|
69
|
+
- `schema/derec_descriptor.bin` — the same closure precompiled, well-known
|
|
70
|
+
types included. Needs no include path at all. Compiled with
|
|
71
|
+
`--include_source_info`, so code generated from it keeps the protocol's doc
|
|
72
|
+
comments instead of emitting bare type declarations.
|
|
73
|
+
|
|
74
|
+
The schema is versioned with the package: `vX.Y.Z` carries exactly the schema
|
|
75
|
+
`vX.Y.Z` was built from.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
61
79
|
## Quickstart: pairing two devices
|
|
62
80
|
|
|
63
81
|
`DeRecProtocol` is the orchestrator: it owns protocol state, drives flows to
|
|
@@ -121,7 +139,7 @@ declared in `src/types.ts`:
|
|
|
121
139
|
- **`UserSecretStore`** — the most recent user-facing secret snapshot per
|
|
122
140
|
`secretId`, used to auto-publish to newly paired peers.
|
|
123
141
|
- **`StateStore`** — in-flight orchestrator bookkeeping (pending
|
|
124
|
-
verifications, recoveries, unpairs, sharing rounds,
|
|
142
|
+
verifications, recoveries, unpairs, sharing rounds, replica discoveries) so a
|
|
125
143
|
multi-round flow survives an app restart.
|
|
126
144
|
- **`Transport`** — outbound delivery. `send(endpoint, message)` posts a
|
|
127
145
|
wire-format envelope to a peer; it is a mailbox, not a request/response
|
|
@@ -131,6 +149,32 @@ Every method is `async` and keyed by decimal-string ids — see the next
|
|
|
131
149
|
section for why that differs from `DeRecProtocol`'s own methods. Full field-
|
|
132
150
|
level contracts are documented as TSDoc on each interface in `src/types.ts`.
|
|
133
151
|
|
|
152
|
+
### Filtered listings
|
|
153
|
+
|
|
154
|
+
`listHelpers` and `listReplicas` receive a filter object — `ids`, `status`,
|
|
155
|
+
`role` and `exclude`, where every empty value means "do not restrict on this",
|
|
156
|
+
so an all-empty filter selects everything. `exclude` is applied last,
|
|
157
|
+
overriding `ids`. Ids are decimal strings, like every other `u64` here.
|
|
158
|
+
|
|
159
|
+
**Apply it in your query.** That is the point: a `WHERE` clause or a
|
|
160
|
+
key-condition expression instead of transferring rows the caller will discard.
|
|
161
|
+
That transfer costs bandwidth everywhere, and on a metered backing such as
|
|
162
|
+
DynamoDB, which bills by bytes read, it costs money.
|
|
163
|
+
|
|
164
|
+
**A store that ignores it is slow, not wrong.** The library re-applies the
|
|
165
|
+
filter to every listing before acting on it and drops anything the filter
|
|
166
|
+
excluded.
|
|
167
|
+
|
|
168
|
+
**That is a one-way guarantee, not a validation of your store.** Dropping rows
|
|
169
|
+
enforces an upper bound; it cannot recover a row you omitted. A store that
|
|
170
|
+
returns *fewer* rows than the filter selects is still wrong, in a way nothing
|
|
171
|
+
in the library can detect — the protocol simply fails to act. Applying the
|
|
172
|
+
filter faithfully is still your job.
|
|
173
|
+
|
|
174
|
+
The backstop matters here in particular: TypeScript accepts a function of fewer
|
|
175
|
+
parameters where more are declared, so a store written before the filter
|
|
176
|
+
existed still satisfies this interface and compiles without a diagnostic.
|
|
177
|
+
|
|
134
178
|
---
|
|
135
179
|
|
|
136
180
|
## Threading model
|
|
@@ -171,7 +215,7 @@ after whatever is in flight.
|
|
|
171
215
|
|
|
172
216
|
## Known differences from `@derec-alliance/nodejs`
|
|
173
217
|
|
|
174
|
-
**`setCommunicationInfo()` and `
|
|
218
|
+
**`setCommunicationInfo()`, `setOwnTransport()` and `setOwnTransports()` return a `Promise`.**
|
|
175
219
|
`@derec-alliance/nodejs` declares both `void`. Here they must not run on the
|
|
176
220
|
JavaScript thread: the underlying FFI setter takes the same handle lock a
|
|
177
221
|
running flow holds across its store callbacks, and those callbacks block the
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/cpp/Primitives.cpp
CHANGED
|
@@ -263,7 +263,7 @@ jsi::Value readTraceId(jsi::Runtime& rt,
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
/// `create_contact_message(channelId: bigint, contactMode: number,
|
|
266
|
-
///
|
|
266
|
+
/// transportProtocols: ArrayBuffer, hasNonce: number, nonce: bigint)
|
|
267
267
|
/// -> { contact_wire_bytes, secret_key_material }`
|
|
268
268
|
jsi::Value createContactMessage(jsi::Runtime& rt,
|
|
269
269
|
const jsi::Value&,
|
|
@@ -272,11 +272,11 @@ jsi::Value createContactMessage(jsi::Runtime& rt,
|
|
|
272
272
|
requireArgs(rt, "create_contact_message", count, 5);
|
|
273
273
|
uint64_t channelId = asU64(rt, args[0]);
|
|
274
274
|
auto contactMode = static_cast<int32_t>(args[1].asNumber());
|
|
275
|
-
ByteView
|
|
275
|
+
ByteView transportProtocols = asBytes(rt, args[2]);
|
|
276
276
|
auto hasNonce = static_cast<uint32_t>(args[3].asNumber());
|
|
277
277
|
uint64_t nonce = asU64(rt, args[4]);
|
|
278
278
|
CreateContactMessageResult result = create_contact_message(
|
|
279
|
-
channelId, contactMode,
|
|
279
|
+
channelId, contactMode, transportProtocols.ptr, transportProtocols.len,
|
|
280
280
|
hasNonce, nonce);
|
|
281
281
|
if (result.error.code != 0) {
|
|
282
282
|
throwDeRecError(rt, result.error);
|
|
@@ -379,7 +379,7 @@ jsi::Value encodeMessageJson(jsi::Runtime& rt,
|
|
|
379
379
|
return toArrayBuffer(rt, bytes.data(), bytes.size());
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
-
/// `produce_pair_request_message(senderKind: number,
|
|
382
|
+
/// `produce_pair_request_message(senderKind: number, transportProtocols:
|
|
383
383
|
/// ArrayBuffer, contactMessage: ArrayBuffer, communicationInfo:
|
|
384
384
|
/// ArrayBuffer|null, parameterRange: ArrayBuffer|null) ->
|
|
385
385
|
/// { request_wire_bytes, initiator_contact_message_wire_bytes,
|
|
@@ -390,12 +390,12 @@ jsi::Value producePairRequestMessage(jsi::Runtime& rt,
|
|
|
390
390
|
size_t count) {
|
|
391
391
|
requireArgs(rt, "produce_pair_request_message", count, 5);
|
|
392
392
|
auto senderKind = static_cast<int32_t>(args[0].asNumber());
|
|
393
|
-
ByteView
|
|
393
|
+
ByteView transportProtocols = asBytes(rt, args[1]);
|
|
394
394
|
ByteView contactMessage = asBytes(rt, args[2]);
|
|
395
395
|
ByteView communicationInfo = asBytes(rt, args[3]);
|
|
396
396
|
ByteView parameterRange = asBytes(rt, args[4]);
|
|
397
397
|
ProducePairRequestMessageResult result = produce_pair_request_message(
|
|
398
|
-
senderKind,
|
|
398
|
+
senderKind, transportProtocols.ptr, transportProtocols.len,
|
|
399
399
|
contactMessage.ptr, contactMessage.len, communicationInfo.ptr,
|
|
400
400
|
communicationInfo.len, parameterRange.ptr, parameterRange.len);
|
|
401
401
|
if (result.error.code != 0) {
|
|
@@ -440,35 +440,41 @@ jsi::Value extractPairRequest(jsi::Runtime& rt,
|
|
|
440
440
|
|
|
441
441
|
/// `produce_pair_response_message(channelId: bigint, requestProto:
|
|
442
442
|
/// ArrayBuffer, secretKeyMaterial: ArrayBuffer, communicationInfo:
|
|
443
|
-
/// ArrayBuffer|null, parameterRange: ArrayBuffer|null
|
|
444
|
-
///
|
|
443
|
+
/// ArrayBuffer|null, parameterRange: ArrayBuffer|null,
|
|
444
|
+
/// unsafeConnection: boolean) ->
|
|
445
|
+
/// { response_wire_bytes, peer_transports, shared_key, channel_id }`
|
|
446
|
+
///
|
|
447
|
+
/// `peer_transports` is a length-delimited sequence of encoded
|
|
448
|
+
/// `TransportProtocol` messages — every endpoint the requester advertised,
|
|
449
|
+
/// in its own order, filtered to those the library will record. It crosses
|
|
450
|
+
/// this seam framed; the TypeScript layer decodes it.
|
|
445
451
|
jsi::Value producePairResponseMessage(jsi::Runtime& rt,
|
|
446
452
|
const jsi::Value&,
|
|
447
453
|
const jsi::Value* args,
|
|
448
454
|
size_t count) {
|
|
449
|
-
requireArgs(rt, "produce_pair_response_message", count,
|
|
455
|
+
requireArgs(rt, "produce_pair_response_message", count, 6);
|
|
450
456
|
uint64_t channelId = asU64(rt, args[0]);
|
|
451
457
|
ByteView requestProto = asBytes(rt, args[1]);
|
|
452
458
|
ByteView secretKeyMaterial = asBytes(rt, args[2]);
|
|
453
459
|
ByteView communicationInfo = asBytes(rt, args[3]);
|
|
454
460
|
ByteView parameterRange = asBytes(rt, args[4]);
|
|
461
|
+
uint32_t unsafeConnection = args[5].asBool() ? 1u : 0u;
|
|
455
462
|
ProducePairResponseMessageResult result = produce_pair_response_message(
|
|
456
463
|
channelId, requestProto.ptr, requestProto.len, secretKeyMaterial.ptr,
|
|
457
464
|
secretKeyMaterial.len, communicationInfo.ptr, communicationInfo.len,
|
|
458
|
-
parameterRange.ptr, parameterRange.len);
|
|
465
|
+
parameterRange.ptr, parameterRange.len, unsafeConnection);
|
|
459
466
|
if (result.error.code != 0) {
|
|
460
467
|
throwDeRecError(rt, result.error);
|
|
461
468
|
}
|
|
462
469
|
std::vector<uint8_t> responseBytes = takeBuffer(result.response_wire_bytes);
|
|
463
|
-
std::vector<uint8_t>
|
|
464
|
-
takeBuffer(result.peer_transport_protocol);
|
|
470
|
+
std::vector<uint8_t> peerTransports = takeBuffer(result.peer_transports);
|
|
465
471
|
std::vector<uint8_t> sharedKey = takeBuffer(result.shared_key);
|
|
466
472
|
auto payload = jsi::Object(rt);
|
|
467
473
|
payload.setProperty(rt, "response_wire_bytes",
|
|
468
474
|
toArrayBuffer(rt, responseBytes.data(), responseBytes.size()));
|
|
469
475
|
payload.setProperty(
|
|
470
|
-
rt, "
|
|
471
|
-
toArrayBuffer(rt,
|
|
476
|
+
rt, "peer_transports",
|
|
477
|
+
toArrayBuffer(rt, peerTransports.data(), peerTransports.size()));
|
|
472
478
|
payload.setProperty(rt, "shared_key",
|
|
473
479
|
toArrayBuffer(rt, sharedKey.data(), sharedKey.size()));
|
|
474
480
|
payload.setProperty(rt, "channel_id",
|
|
@@ -525,17 +531,20 @@ jsi::Value processPairResponseMessage(jsi::Runtime& rt,
|
|
|
525
531
|
return jsi::Value(rt, payload);
|
|
526
532
|
}
|
|
527
533
|
|
|
528
|
-
/// `produce_pre_pair_request_message(
|
|
534
|
+
/// `produce_pre_pair_request_message(transportProtocols: ArrayBuffer,
|
|
529
535
|
/// contactMessage: ArrayBuffer) -> ArrayBuffer`
|
|
536
|
+
///
|
|
537
|
+
/// `transportProtocols` is a length-delimited sequence of encoded
|
|
538
|
+
/// `TransportProtocol` messages in the scanner's preference order.
|
|
530
539
|
jsi::Value producePrePairRequestMessage(jsi::Runtime& rt,
|
|
531
540
|
const jsi::Value&,
|
|
532
541
|
const jsi::Value* args,
|
|
533
542
|
size_t count) {
|
|
534
543
|
requireArgs(rt, "produce_pre_pair_request_message", count, 2);
|
|
535
|
-
ByteView
|
|
544
|
+
ByteView transportProtocols = asBytes(rt, args[0]);
|
|
536
545
|
ByteView contactMessage = asBytes(rt, args[1]);
|
|
537
546
|
ProducePrePairRequestMessageResult result = produce_pre_pair_request_message(
|
|
538
|
-
|
|
547
|
+
transportProtocols.ptr, transportProtocols.len, contactMessage.ptr,
|
|
539
548
|
contactMessage.len);
|
|
540
549
|
if (result.error.code != 0) {
|
|
541
550
|
throwDeRecError(rt, result.error);
|
|
@@ -1145,7 +1154,7 @@ void installPrimitives(jsi::Runtime& rt, jsi::Object& host) {
|
|
|
1145
1154
|
bind(rt, host, "encode_message_json", 2, encodeMessageJson);
|
|
1146
1155
|
bind(rt, host, "produce_pair_request_message", 5, producePairRequestMessage);
|
|
1147
1156
|
bind(rt, host, "extract_pair_request", 2, extractPairRequest);
|
|
1148
|
-
bind(rt, host, "produce_pair_response_message",
|
|
1157
|
+
bind(rt, host, "produce_pair_response_message", 7, producePairResponseMessage);
|
|
1149
1158
|
bind(rt, host, "extract_pair_response", 2, extractPairResponse);
|
|
1150
1159
|
bind(rt, host, "process_pair_response_message", 3, processPairResponseMessage);
|
|
1151
1160
|
bind(rt, host, "produce_pre_pair_request_message", 2,
|
package/cpp/ProtocolHost.cpp
CHANGED
|
@@ -320,9 +320,45 @@ jsi::Value ProtocolHost::get(jsi::Runtime& rt, const jsi::PropNameID& name) {
|
|
|
320
320
|
DeRecProtocolHandle* handle = handle_;
|
|
321
321
|
|
|
322
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"
|
|
323
328
|
DeRecError error = derec_protocol_set_own_transport(
|
|
324
329
|
handle, reinterpret_cast<const uint8_t*>(uri.data()), uri.size(),
|
|
325
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
|
+
// Takes the endpoint list already JSON-encoded, the same way
|
|
345
|
+
// `setCommunicationInfo` takes its map: the FFI parses JSON on the other
|
|
346
|
+
// side, so encoding in JavaScript keeps this bridge free of a shape it
|
|
347
|
+
// would otherwise have to marshal field by field.
|
|
348
|
+
if (prop == "setOwnTransports") {
|
|
349
|
+
return jsi::Function::createFromHostFunction(
|
|
350
|
+
rt, name, 1,
|
|
351
|
+
[this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value* args,
|
|
352
|
+
size_t count) -> jsi::Value {
|
|
353
|
+
requireArgs(rt2, "setOwnTransports", count, 1);
|
|
354
|
+
ByteView view = asBytes(rt2, args[0]);
|
|
355
|
+
std::vector<uint8_t> transports(view.ptr, view.ptr + view.len);
|
|
356
|
+
DeRecProtocolHandle* handle = handle_;
|
|
357
|
+
|
|
358
|
+
auto body = [handle,
|
|
359
|
+
transports = std::move(transports)]() -> std::vector<uint8_t> {
|
|
360
|
+
DeRecError error = derec_protocol_set_own_transports(
|
|
361
|
+
handle, transports.data(), transports.size());
|
|
326
362
|
if (error.code != DEREC_CODE_OK) {
|
|
327
363
|
throw FfiFailure(error);
|
|
328
364
|
}
|
|
@@ -597,6 +633,7 @@ jsi::Value ProtocolHost::get(jsi::Runtime& rt, const jsi::PropNameID& name) {
|
|
|
597
633
|
std::vector<jsi::PropNameID> ProtocolHost::getPropertyNames(jsi::Runtime& rt) {
|
|
598
634
|
const char* names[] = {
|
|
599
635
|
"secretId", "setCommunicationInfo", "setOwnTransport",
|
|
636
|
+
"setOwnTransports",
|
|
600
637
|
"createContact", "start", "process",
|
|
601
638
|
"tick", "accept", "reject",
|
|
602
639
|
"getFingerprint", "verifyFingerprint", "removeExpiredChannels",
|
package/cpp/StoreCallbacks.cpp
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include "StoreCallbacks.h"
|
|
5
5
|
|
|
6
6
|
#include <cstring>
|
|
7
|
+
#include <optional>
|
|
7
8
|
#include <string>
|
|
8
9
|
#include <vector>
|
|
9
10
|
|
|
@@ -577,19 +578,31 @@ extern "C" int32_t channelStoreRemove(void* userData, uint64_t secretId, uint64_
|
|
|
577
578
|
return result.code;
|
|
578
579
|
}
|
|
579
580
|
|
|
580
|
-
/// `ChannelStore.listHelpers(secretId) -> Uint8Array | null`
|
|
581
|
-
|
|
582
|
-
|
|
581
|
+
/// `ChannelStore.listHelpers(secretId, filter) -> Uint8Array | null`
|
|
582
|
+
///
|
|
583
|
+
/// `filter` arrives as JSON and is handed to JavaScript as a plain object —
|
|
584
|
+
/// see `ChannelFilter` in the SDK's types. The buffer is owned by the core and
|
|
585
|
+
/// valid only for this call, so it is parsed before the promise is awaited.
|
|
586
|
+
extern "C" int32_t channelStoreListHelpers(void* userData, uint64_t secretId, const uint8_t* filter,
|
|
587
|
+
size_t filterLen, uint8_t** outPtr, size_t* outLen) {
|
|
583
588
|
auto* self = static_cast<StoreBindings*>(userData);
|
|
584
589
|
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
585
590
|
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
586
591
|
// owner has released its own reference — has somewhere safe to run.
|
|
587
592
|
auto keepAlive = self->shared_from_this();
|
|
588
|
-
|
|
593
|
+
std::vector<uint8_t> filterJson(filter, filter + filterLen);
|
|
594
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, secretId, filterJson](std::function<void(CallResult)> settle) {
|
|
589
595
|
jsi::Runtime& rt = keepAlive->runtime();
|
|
590
596
|
auto store = keepAlive->channelStore();
|
|
591
597
|
auto method = store->getPropertyAsFunction(rt, "listHelpers");
|
|
592
|
-
|
|
598
|
+
// Ids arrive as decimal strings — the core encodes them that way because
|
|
599
|
+
// `JSON.parse` cannot hold a u64; see `encode_filter` in
|
|
600
|
+
// `interop/ffi/protocol/stores.rs`.
|
|
601
|
+
jsi::Value filterVal = filterJson.empty()
|
|
602
|
+
? jsi::Value(jsi::Object(rt))
|
|
603
|
+
: jsonParseUtf8(rt, filterJson.data(), filterJson.size());
|
|
604
|
+
jsi::Value promise =
|
|
605
|
+
method.callWithThis(rt, *store, idVal(rt, secretId), std::move(filterVal));
|
|
593
606
|
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toCallResult);
|
|
594
607
|
});
|
|
595
608
|
if (result.code == 0) {
|
|
@@ -603,19 +616,31 @@ extern "C" int32_t channelStoreListHelpers(void* userData, uint64_t secretId, ui
|
|
|
603
616
|
return result.code;
|
|
604
617
|
}
|
|
605
618
|
|
|
606
|
-
/// `ChannelStore.listReplicas(secretId) -> Uint8Array | null`
|
|
607
|
-
|
|
608
|
-
|
|
619
|
+
/// `ChannelStore.listReplicas(secretId, filter) -> Uint8Array | null`
|
|
620
|
+
///
|
|
621
|
+
/// `filter` arrives as JSON and is handed to JavaScript as a plain object —
|
|
622
|
+
/// see `ChannelFilter` in the SDK's types. The buffer is owned by the core and
|
|
623
|
+
/// valid only for this call, so it is parsed before the promise is awaited.
|
|
624
|
+
extern "C" int32_t channelStoreListReplicas(void* userData, uint64_t secretId, const uint8_t* filter,
|
|
625
|
+
size_t filterLen, uint8_t** outPtr, size_t* outLen) {
|
|
609
626
|
auto* self = static_cast<StoreBindings*>(userData);
|
|
610
627
|
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
611
628
|
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
612
629
|
// owner has released its own reference — has somewhere safe to run.
|
|
613
630
|
auto keepAlive = self->shared_from_this();
|
|
614
|
-
|
|
631
|
+
std::vector<uint8_t> filterJson(filter, filter + filterLen);
|
|
632
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, secretId, filterJson](std::function<void(CallResult)> settle) {
|
|
615
633
|
jsi::Runtime& rt = keepAlive->runtime();
|
|
616
634
|
auto store = keepAlive->channelStore();
|
|
617
635
|
auto method = store->getPropertyAsFunction(rt, "listReplicas");
|
|
618
|
-
|
|
636
|
+
// Ids arrive as decimal strings — the core encodes them that way because
|
|
637
|
+
// `JSON.parse` cannot hold a u64; see `encode_filter` in
|
|
638
|
+
// `interop/ffi/protocol/stores.rs`.
|
|
639
|
+
jsi::Value filterVal = filterJson.empty()
|
|
640
|
+
? jsi::Value(jsi::Object(rt))
|
|
641
|
+
: jsonParseUtf8(rt, filterJson.data(), filterJson.size());
|
|
642
|
+
jsi::Value promise =
|
|
643
|
+
method.callWithThis(rt, *store, idVal(rt, secretId), std::move(filterVal));
|
|
619
644
|
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toCallResult);
|
|
620
645
|
});
|
|
621
646
|
if (result.code == 0) {
|
|
@@ -1073,27 +1098,110 @@ extern "C" int32_t stateStoreLoadAll(void* userData, uint64_t secretId, uint32_t
|
|
|
1073
1098
|
/// is reported rather than guessed at.
|
|
1074
1099
|
std::string protocolToString(int32_t protocol) { return protocol == 0 ? "https" : "unknown"; }
|
|
1075
1100
|
|
|
1076
|
-
/// `
|
|
1077
|
-
|
|
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
|
+
/// `Transport.send(endpoints, message) -> void`
|
|
1154
|
+
///
|
|
1155
|
+
/// `endpoints` is every address the peer advertised, in the peer's order,
|
|
1156
|
+
/// already filtered to those the library will record. The library does not
|
|
1157
|
+
/// rank them: which to dial, and whether to fall back, is the JavaScript
|
|
1158
|
+
/// implementation's choice. Delivery to any one of them is success.
|
|
1159
|
+
extern "C" int32_t transportSend(void* userData, const uint8_t* endpointsPtr, size_t endpointsLen,
|
|
1078
1160
|
const uint8_t* bytes, size_t len) {
|
|
1079
1161
|
auto* self = static_cast<StoreBindings*>(userData);
|
|
1080
1162
|
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
1081
1163
|
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
1082
1164
|
// owner has released its own reference — has somewhere safe to run.
|
|
1083
1165
|
auto keepAlive = self->shared_from_this();
|
|
1084
|
-
|
|
1166
|
+
|
|
1167
|
+
// Length-delimited TransportProtocol sequence: each entry preceded by its
|
|
1168
|
+
// protobuf varint byte length.
|
|
1169
|
+
std::vector<std::pair<std::string, int32_t>> endpoints;
|
|
1170
|
+
size_t offset = 0;
|
|
1171
|
+
while (offset < endpointsLen) {
|
|
1172
|
+
uint64_t size = 0;
|
|
1173
|
+
int shift = 0;
|
|
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;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1085
1187
|
std::vector<uint8_t> message(bytes, bytes + len);
|
|
1086
|
-
CallResult result = keepAlive->bridge().callSync([keepAlive,
|
|
1188
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, endpoints,
|
|
1087
1189
|
message](std::function<void(CallResult)> settle) {
|
|
1088
1190
|
jsi::Runtime& rt = keepAlive->runtime();
|
|
1089
1191
|
auto store = keepAlive->transportStore();
|
|
1090
1192
|
auto method = store->getPropertyAsFunction(rt, "send");
|
|
1091
|
-
jsi::
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1193
|
+
jsi::Array endpointsJs(rt, endpoints.size());
|
|
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
|
+
}
|
|
1095
1203
|
jsi::Value promise =
|
|
1096
|
-
method.callWithThis(rt, *store, jsi::Value(rt,
|
|
1204
|
+
method.callWithThis(rt, *store, jsi::Value(rt, endpointsJs), toUint8ArrayVal(rt, message));
|
|
1097
1205
|
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toVoidResult);
|
|
1098
1206
|
});
|
|
1099
1207
|
return result.code;
|