@derec-alliance/react-native 0.0.5 → 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 +207 -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 +221 -96
package/README.md
CHANGED
|
@@ -98,7 +98,7 @@ const protocol = new DeRecProtocolBuilder(secretId) // bigint | number
|
|
|
98
98
|
.withUserSecretStore(myUserSecretStore)
|
|
99
99
|
.withStateStore(myStateStore)
|
|
100
100
|
.withTransport(myTransport)
|
|
101
|
-
.
|
|
101
|
+
.withOwnTransports([{ uri: 'https://owner.example.com', protocol: 'https' }])
|
|
102
102
|
.withThreshold(2)
|
|
103
103
|
.build();
|
|
104
104
|
|
|
@@ -141,9 +141,14 @@ declared in `src/types.ts`:
|
|
|
141
141
|
- **`StateStore`** — in-flight orchestrator bookkeeping (pending
|
|
142
142
|
verifications, recoveries, unpairs, sharing rounds, replica discoveries) so a
|
|
143
143
|
multi-round flow survives an app restart.
|
|
144
|
-
- **`Transport`** — outbound delivery.
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
- **`Transport`** — outbound delivery.
|
|
145
|
+
`send(endpoints: ReadonlyArray<{ protocol: string; uri: string }>, message: Uint8Array): Promise<void>`
|
|
146
|
+
posts a wire-format envelope to a peer. `endpoints` is every address that
|
|
147
|
+
peer advertised, in its own order (`protocol` is `"https"` or `"grpc"`);
|
|
148
|
+
deliver once, to the first one that accepts it, and reject only when none
|
|
149
|
+
did. `sequentialFailover` and `singleEndpointTransport` implement that
|
|
150
|
+
choice for you. It is a mailbox, not a request/response call, since not
|
|
151
|
+
every peer (e.g. a phone behind NAT) can be dialed back.
|
|
147
152
|
|
|
148
153
|
Every method is `async` and keyed by decimal-string ids — see the next
|
|
149
154
|
section for why that differs from `DeRecProtocol`'s own methods. Full field-
|
|
@@ -215,17 +220,6 @@ after whatever is in flight.
|
|
|
215
220
|
|
|
216
221
|
## Known differences from `@derec-alliance/nodejs`
|
|
217
222
|
|
|
218
|
-
**`setCommunicationInfo()`, `setOwnTransport()` and `setOwnTransports()` return a `Promise`.**
|
|
219
|
-
`@derec-alliance/nodejs` declares both `void`. Here they must not run on the
|
|
220
|
-
JavaScript thread: the underlying FFI setter takes the same handle lock a
|
|
221
|
-
running flow holds across its store callbacks, and those callbacks block the
|
|
222
|
-
worker until JavaScript settles them — so a synchronous setter would block
|
|
223
|
-
the JavaScript thread on a lock only the JavaScript thread can release. The
|
|
224
|
-
binding queues them onto the same serial worker every flow runs on instead.
|
|
225
|
-
The nodejs SDK is WASM and single-threaded, so it cannot hit this. Code that
|
|
226
|
-
ignores the return value — the way nodejs code calls these — is unaffected;
|
|
227
|
-
awaiting them additionally lets a failure surface instead of vanishing.
|
|
228
|
-
|
|
229
223
|
**The `DeRecErrorCategory` union differs from nodejs's.** This SDK's union
|
|
230
224
|
omits `"wasm"`, which a C-ABI binding can never emit, and adds `"ok"`,
|
|
231
225
|
`"ffi"` and `"state_store"`, which it can. A test pins the union to the crate's shared enum fixture so it stays in step
|
package/android/CMakeLists.txt
CHANGED
|
@@ -14,7 +14,7 @@ find_package(fbjni REQUIRED CONFIG)
|
|
|
14
14
|
|
|
15
15
|
# Non-recursive on purpose: `cpp/tests/` holds the host-only test harness,
|
|
16
16
|
# which must not be compiled into the device library.
|
|
17
|
-
file(GLOB DEREC_CPP_SOURCES "${CMAKE_SOURCE_DIR}/../cpp/*.cpp")
|
|
17
|
+
file(GLOB DEREC_CPP_SOURCES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/../cpp/*.cpp")
|
|
18
18
|
|
|
19
19
|
add_library(derec_jsi SHARED
|
|
20
20
|
${DEREC_CPP_SOURCES}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/cpp/Convert.cpp
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
#include <cstdlib>
|
|
7
7
|
#include <cstring>
|
|
8
|
+
#include <utility>
|
|
8
9
|
|
|
9
10
|
namespace derec {
|
|
10
11
|
|
|
@@ -33,6 +34,23 @@ std::vector<uint8_t> takeBuffer(DeRecBuffer& buffer) {
|
|
|
33
34
|
return out;
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
RestoreOutcome takeRestoreResult(DeRecProtocolRestoreResult& result) {
|
|
38
|
+
RestoreOutcome out{result.error, {}, {}};
|
|
39
|
+
try {
|
|
40
|
+
out.events = takeBuffer(result.events_json);
|
|
41
|
+
out.conflictingChannelIdsJson = takeBuffer(result.conflicting_channel_ids_json);
|
|
42
|
+
} catch (...) {
|
|
43
|
+
// `takeBuffer` clears a buffer only after releasing it, so whichever is
|
|
44
|
+
// still set here was never released.
|
|
45
|
+
derec_free_buffer(result.events_json.ptr, result.events_json.len);
|
|
46
|
+
derec_free_buffer(result.conflicting_channel_ids_json.ptr,
|
|
47
|
+
result.conflicting_channel_ids_json.len);
|
|
48
|
+
derec_free_error(&result.error);
|
|
49
|
+
throw;
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
36
54
|
std::string takeString(char* owned) {
|
|
37
55
|
if (owned == nullptr) {
|
|
38
56
|
return {};
|
|
@@ -47,4 +65,111 @@ std::pair<std::string, std::string> errorName(int32_t category, int32_t code) {
|
|
|
47
65
|
std::string(derec_error_code_name(code))};
|
|
48
66
|
}
|
|
49
67
|
|
|
68
|
+
|
|
69
|
+
// Minimal decoders for the wire's "bare JSON array/object of unsigned
|
|
70
|
+
// integers" convention (`versions_json`, `channel_ids_json`, the `bytes`
|
|
71
|
+
// field of a `ShareRecord` / `SecretValueRecord`). These are produced by
|
|
72
|
+
// `serde_json`'s default compact, whitespace-free output over a fixed,
|
|
73
|
+
// known shape, so a substring scan is sufficient and — critically — never
|
|
74
|
+
// routes a `u64` through a JS `number`, which cannot represent every value
|
|
75
|
+
// in that range exactly.
|
|
76
|
+
|
|
77
|
+
namespace {
|
|
78
|
+
|
|
79
|
+
/// Offset just past `"key":` in a flat, compact JSON object, or
|
|
80
|
+
/// `std::string::npos` if `key` is absent.
|
|
81
|
+
size_t fieldValueOffset(const std::string& json, const char* key) {
|
|
82
|
+
std::string needle = std::string("\"") + key + "\":";
|
|
83
|
+
size_t pos = json.find(needle);
|
|
84
|
+
return pos == std::string::npos ? std::string::npos : pos + needle.size();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
uint64_t parseUnsignedAt(const std::string& json, size_t pos) {
|
|
88
|
+
uint64_t value = 0;
|
|
89
|
+
while (pos < json.size() && json[pos] >= '0' && json[pos] <= '9') {
|
|
90
|
+
value = value * 10 + static_cast<uint64_t>(json[pos] - '0');
|
|
91
|
+
++pos;
|
|
92
|
+
}
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
std::vector<uint8_t> parseByteArrayAt(const std::string& json, size_t pos) {
|
|
97
|
+
std::vector<uint8_t> out;
|
|
98
|
+
while (pos < json.size() && json[pos] != '[') ++pos;
|
|
99
|
+
if (pos >= json.size()) return out;
|
|
100
|
+
++pos;
|
|
101
|
+
while (pos < json.size() && json[pos] != ']') {
|
|
102
|
+
if (json[pos] < '0' || json[pos] > '9') {
|
|
103
|
+
++pos;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
uint64_t value = 0;
|
|
107
|
+
while (pos < json.size() && json[pos] >= '0' && json[pos] <= '9') {
|
|
108
|
+
value = value * 10 + static_cast<uint64_t>(json[pos] - '0');
|
|
109
|
+
++pos;
|
|
110
|
+
}
|
|
111
|
+
out.push_back(static_cast<uint8_t>(value));
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// The string inside `"key":"..."` in a flat, compact JSON object, or empty
|
|
117
|
+
/// if `key` is absent.
|
|
118
|
+
std::optional<std::string> quotedFieldValue(const std::string& json, const char* key) {
|
|
119
|
+
size_t pos = fieldValueOffset(json, key);
|
|
120
|
+
if (pos == std::string::npos || pos >= json.size() || json[pos] != '"') return std::nullopt;
|
|
121
|
+
size_t end = json.find('"', pos + 1);
|
|
122
|
+
if (end == std::string::npos) return std::nullopt;
|
|
123
|
+
return json.substr(pos + 1, end - pos - 1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
} // namespace
|
|
127
|
+
|
|
128
|
+
/// Parse a bare `[n, n, ...]` array of unsigned decimal integers.
|
|
129
|
+
std::vector<uint64_t> parseUnsignedJsonArray(const uint8_t* ptr, size_t len) {
|
|
130
|
+
std::vector<uint64_t> out;
|
|
131
|
+
size_t i = 0;
|
|
132
|
+
while (i < len && ptr[i] != '[') ++i;
|
|
133
|
+
if (i >= len) return out;
|
|
134
|
+
++i;
|
|
135
|
+
while (i < len && ptr[i] != ']') {
|
|
136
|
+
if (ptr[i] < '0' || ptr[i] > '9') {
|
|
137
|
+
++i;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
uint64_t value = 0;
|
|
141
|
+
while (i < len && ptr[i] >= '0' && ptr[i] <= '9') {
|
|
142
|
+
value = value * 10 + static_cast<uint64_t>(ptr[i] - '0');
|
|
143
|
+
++i;
|
|
144
|
+
}
|
|
145
|
+
out.push_back(value);
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// `{"kind":<n>,"bytes":[<n>,...]}` — the wire shape `SecretStore.save`
|
|
151
|
+
/// receives; `kind` is already carried as a separate FFI argument so only
|
|
152
|
+
/// `bytes` needs to be read back out.
|
|
153
|
+
std::vector<uint8_t> decodeSecretValueBytes(const uint8_t* ptr, size_t len) {
|
|
154
|
+
std::string json(reinterpret_cast<const char*>(ptr), len);
|
|
155
|
+
size_t pos = fieldValueOffset(json, "bytes");
|
|
156
|
+
if (pos == std::string::npos) return {};
|
|
157
|
+
return parseByteArrayAt(json, pos);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
std::optional<DecodedShare> decodeShareRecord(const uint8_t* ptr, size_t len) {
|
|
161
|
+
std::string json(reinterpret_cast<const char*>(ptr), len);
|
|
162
|
+
std::optional<std::string> secretId = quotedFieldValue(json, "secret_id");
|
|
163
|
+
size_t versionPos = fieldValueOffset(json, "version");
|
|
164
|
+
size_t bytesPos = fieldValueOffset(json, "bytes");
|
|
165
|
+
if (!secretId || versionPos == std::string::npos || bytesPos == std::string::npos) {
|
|
166
|
+
return std::nullopt;
|
|
167
|
+
}
|
|
168
|
+
DecodedShare out;
|
|
169
|
+
out.secretId = std::move(*secretId);
|
|
170
|
+
out.version = static_cast<uint32_t>(parseUnsignedAt(json, versionPos));
|
|
171
|
+
out.bytes = parseByteArrayAt(json, bytesPos);
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
|
|
50
175
|
} // namespace derec
|
package/cpp/Convert.h
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
#include <cstddef>
|
|
7
7
|
#include <cstdint>
|
|
8
|
+
#include <optional>
|
|
8
9
|
#include <string>
|
|
9
10
|
#include <utility>
|
|
10
11
|
#include <vector>
|
|
@@ -32,7 +33,41 @@ std::vector<uint8_t> takeBuffer(DeRecBuffer& buffer);
|
|
|
32
33
|
/// Copy a Rust-owned C string and release it via `derec_free_string`.
|
|
33
34
|
std::string takeString(char* owned);
|
|
34
35
|
|
|
36
|
+
/// Every owned part of a `DeRecProtocolRestoreResult`, copied out with both
|
|
37
|
+
/// buffers already released. `error`'s strings stay owned: they are released
|
|
38
|
+
/// by `throwDeRecError`, as for every other result.
|
|
39
|
+
struct RestoreOutcome {
|
|
40
|
+
DeRecError error;
|
|
41
|
+
std::vector<uint8_t> events;
|
|
42
|
+
std::vector<uint8_t> conflictingChannelIdsJson;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/// Releases both buffers of `result` on every path, including a failed copy.
|
|
46
|
+
RestoreOutcome takeRestoreResult(DeRecProtocolRestoreResult& result);
|
|
47
|
+
|
|
35
48
|
/// Resolve the `(category, code)` name pair from the crate's accessors.
|
|
36
49
|
std::pair<std::string, std::string> errorName(int32_t category, int32_t code);
|
|
37
50
|
|
|
51
|
+
// Decoders for the payloads Rust hands the store callbacks.
|
|
52
|
+
// They hold no `jsi` state, so the standalone test harness exercises them
|
|
53
|
+
// against the real crate.
|
|
54
|
+
|
|
55
|
+
/// Parse a bare `[n, n, ...]` array of unsigned decimal integers.
|
|
56
|
+
std::vector<uint64_t> parseUnsignedJsonArray(const uint8_t* ptr, size_t len);
|
|
57
|
+
|
|
58
|
+
/// The `bytes` field of a `{"kind":<n>,"bytes":[<n>,...]}` `SecretValueRecord`.
|
|
59
|
+
std::vector<uint8_t> decodeSecretValueBytes(const uint8_t* ptr, size_t len);
|
|
60
|
+
|
|
61
|
+
/// A `ShareRecord` as `ShareStore.save` receives it. `secretId` is the
|
|
62
|
+
/// record's own `secret_id`, kept as the decimal string Rust wrote.
|
|
63
|
+
struct DecodedShare {
|
|
64
|
+
std::string secretId;
|
|
65
|
+
uint32_t version;
|
|
66
|
+
std::vector<uint8_t> bytes;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/// Decode `{"secret_id":"...","version":<n>,"bytes":[<n>,...]}`. Empty when
|
|
70
|
+
/// any of the three fields is absent.
|
|
71
|
+
std::optional<DecodedShare> decodeShareRecord(const uint8_t* ptr, size_t len);
|
|
72
|
+
|
|
38
73
|
} // namespace derec
|
package/cpp/DeRecInstaller.cpp
CHANGED
|
@@ -157,18 +157,6 @@ void install(jsi::Runtime& rt, std::shared_ptr<Invoker> invoker) {
|
|
|
157
157
|
|
|
158
158
|
auto host = jsi::Object(rt);
|
|
159
159
|
|
|
160
|
-
host.setProperty(
|
|
161
|
-
rt, "version",
|
|
162
|
-
jsi::Function::createFromHostFunction(
|
|
163
|
-
rt, jsi::PropNameID::forAscii(rt, "version"), 0,
|
|
164
|
-
[](jsi::Runtime& rt, const jsi::Value&, const jsi::Value*, size_t)
|
|
165
|
-
-> jsi::Value {
|
|
166
|
-
DeRecProtocolVersion version = derec_protocol_version();
|
|
167
|
-
std::string text = std::to_string(version.major) + "." +
|
|
168
|
-
std::to_string(version.minor);
|
|
169
|
-
return jsi::String::createFromUtf8(rt, text);
|
|
170
|
-
}));
|
|
171
|
-
|
|
172
160
|
installPrimitives(rt, host);
|
|
173
161
|
|
|
174
162
|
host.setProperty(
|
package/cpp/Primitives.cpp
CHANGED
|
@@ -14,6 +14,12 @@ using namespace facebook;
|
|
|
14
14
|
namespace derec {
|
|
15
15
|
|
|
16
16
|
void throwDeRecError(jsi::Runtime& rt, const DeRecError& error) {
|
|
17
|
+
throwDeRecError(rt, error, {});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
void throwDeRecError(jsi::Runtime& rt,
|
|
21
|
+
const DeRecError& error,
|
|
22
|
+
const std::vector<uint8_t>& conflictingChannelIdsJson) {
|
|
17
23
|
auto names = errorName(error.category, error.code);
|
|
18
24
|
// `message` and `peer_memo` are Rust-owned strings; copy before releasing.
|
|
19
25
|
std::string message =
|
|
@@ -42,6 +48,11 @@ void throwDeRecError(jsi::Runtime& rt, const DeRecError& error) {
|
|
|
42
48
|
payload.setProperty(rt, "expected", jsi::Value(static_cast<int>(expected)));
|
|
43
49
|
payload.setProperty(rt, "got", jsi::Value(static_cast<int>(got)));
|
|
44
50
|
}
|
|
51
|
+
if (!conflictingChannelIdsJson.empty()) {
|
|
52
|
+
payload.setProperty(rt, "channel_ids",
|
|
53
|
+
jsi::Value::createFromJsonUtf8(rt, conflictingChannelIdsJson.data(),
|
|
54
|
+
conflictingChannelIdsJson.size()));
|
|
55
|
+
}
|
|
45
56
|
throw jsi::JSError(rt, jsi::Value(rt, payload));
|
|
46
57
|
}
|
|
47
58
|
|
|
@@ -74,6 +85,22 @@ jsi::Value toArrayBuffer(jsi::Runtime& rt, const uint8_t* bytes, size_t len) {
|
|
|
74
85
|
return jsi::Value(rt, result);
|
|
75
86
|
}
|
|
76
87
|
|
|
88
|
+
/// Build a genuine `Uint8Array` (not a bare `ArrayBuffer`) — every store
|
|
89
|
+
/// interface types its byte payloads as `Uint8Array`, and application code
|
|
90
|
+
/// is entitled to index or slice it directly.
|
|
91
|
+
jsi::Value toUint8ArrayVal(jsi::Runtime& rt, const uint8_t* bytes, size_t len) {
|
|
92
|
+
jsi::Uint8Array arr(rt, len);
|
|
93
|
+
if (len > 0 && bytes != nullptr) {
|
|
94
|
+
jsi::ArrayBuffer buffer = arr.buffer(rt);
|
|
95
|
+
std::memcpy(buffer.data(rt), bytes, len);
|
|
96
|
+
}
|
|
97
|
+
return jsi::Value(rt, std::move(arr));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
jsi::Value toUint8ArrayVal(jsi::Runtime& rt, const std::vector<uint8_t>& bytes) {
|
|
101
|
+
return toUint8ArrayVal(rt, bytes.data(), bytes.size());
|
|
102
|
+
}
|
|
103
|
+
|
|
77
104
|
uint64_t asU64(jsi::Runtime& rt, const jsi::Value& value) {
|
|
78
105
|
if (value.isBigInt()) {
|
|
79
106
|
return value.asBigInt(rt).asUint64(rt);
|
|
@@ -262,6 +289,28 @@ jsi::Value readTraceId(jsi::Runtime& rt,
|
|
|
262
289
|
return jsi::Value(rt, jsi::BigInt::fromUint64(rt, result.trace_id));
|
|
263
290
|
}
|
|
264
291
|
|
|
292
|
+
/// `version() -> { major: number, minor: number }` — the protocol version the
|
|
293
|
+
/// core writes into every envelope it produces.
|
|
294
|
+
jsi::Value protocolVersion(jsi::Runtime& rt,
|
|
295
|
+
const jsi::Value&,
|
|
296
|
+
const jsi::Value*,
|
|
297
|
+
size_t) {
|
|
298
|
+
DeRecProtocolVersion version = derec_protocol_version();
|
|
299
|
+
auto payload = jsi::Object(rt);
|
|
300
|
+
payload.setProperty(rt, "major", jsi::Value(static_cast<double>(version.major)));
|
|
301
|
+
payload.setProperty(rt, "minor", jsi::Value(static_cast<double>(version.minor)));
|
|
302
|
+
return jsi::Value(rt, payload);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/// `generate_replica_id() -> bigint` — a fresh replica identity from the
|
|
306
|
+
/// core, never `0`. The application persists it once per device.
|
|
307
|
+
jsi::Value generateReplicaId(jsi::Runtime& rt,
|
|
308
|
+
const jsi::Value&,
|
|
309
|
+
const jsi::Value*,
|
|
310
|
+
size_t) {
|
|
311
|
+
return jsi::Value(rt, jsi::BigInt::fromUint64(rt, derec_generate_replica_id()));
|
|
312
|
+
}
|
|
313
|
+
|
|
265
314
|
/// `create_contact_message(channelId: bigint, contactMode: number,
|
|
266
315
|
/// transportProtocols: ArrayBuffer, hasNonce: number, nonce: bigint)
|
|
267
316
|
/// -> { contact_wire_bytes, secret_key_material }`
|
|
@@ -291,21 +340,6 @@ jsi::Value createContactMessage(jsi::Runtime& rt,
|
|
|
291
340
|
return jsi::Value(rt, payload);
|
|
292
341
|
}
|
|
293
342
|
|
|
294
|
-
/// `validate_contact_message(contactMessage: ArrayBuffer) -> undefined`
|
|
295
|
-
jsi::Value validateContactMessage(jsi::Runtime& rt,
|
|
296
|
-
const jsi::Value&,
|
|
297
|
-
const jsi::Value* args,
|
|
298
|
-
size_t count) {
|
|
299
|
-
requireArgs(rt, "validate_contact_message", count, 1);
|
|
300
|
-
ByteView contactMessage = asBytes(rt, args[0]);
|
|
301
|
-
DeRecError error =
|
|
302
|
-
validate_contact_message(contactMessage.ptr, contactMessage.len);
|
|
303
|
-
if (error.code != 0) {
|
|
304
|
-
throwDeRecError(rt, error);
|
|
305
|
-
}
|
|
306
|
-
return jsi::Value::undefined();
|
|
307
|
-
}
|
|
308
|
-
|
|
309
343
|
/// `encode_contact_message(contactJson: ArrayBuffer) -> ArrayBuffer`
|
|
310
344
|
jsi::Value encodeContactMessage(jsi::Runtime& rt,
|
|
311
345
|
const jsi::Value&,
|
|
@@ -338,6 +372,20 @@ jsi::Value decodeContactMessage(jsi::Runtime& rt,
|
|
|
338
372
|
return toArrayBuffer(rt, bytes.data(), bytes.size());
|
|
339
373
|
}
|
|
340
374
|
|
|
375
|
+
/// `transport_protocol_discriminant(name: string) -> number`
|
|
376
|
+
///
|
|
377
|
+
/// The `derec_proto::Protocol` discriminant the crate assigns to `name`, or
|
|
378
|
+
/// `-1` when it names none; forwarded unchanged for Rust to validate.
|
|
379
|
+
jsi::Value transportProtocolDiscriminant(jsi::Runtime& rt,
|
|
380
|
+
const jsi::Value&,
|
|
381
|
+
const jsi::Value* args,
|
|
382
|
+
size_t count) {
|
|
383
|
+
requireArgs(rt, "transport_protocol_discriminant", count, 1);
|
|
384
|
+
std::string name = args[0].asString(rt).utf8(rt);
|
|
385
|
+
return jsi::Value(static_cast<int>(derec_transport_protocol_discriminant(
|
|
386
|
+
reinterpret_cast<const uint8_t*>(name.data()), name.size())));
|
|
387
|
+
}
|
|
388
|
+
|
|
341
389
|
/// `decode_message_json(kind: number, protoBytes: ArrayBuffer) -> ArrayBuffer`
|
|
342
390
|
///
|
|
343
391
|
/// Turns the opaque `*_proto_bytes` an `extract_*` call returns into the JSON
|
|
@@ -415,17 +463,19 @@ jsi::Value producePairRequestMessage(jsi::Runtime& rt,
|
|
|
415
463
|
return jsi::Value(rt, payload);
|
|
416
464
|
}
|
|
417
465
|
|
|
418
|
-
/// `extract_pair_request(request: ArrayBuffer, secretKeyMaterial: ArrayBuffer
|
|
419
|
-
/// -> { channel_id, request_proto_bytes }`
|
|
466
|
+
/// `extract_pair_request(request: ArrayBuffer, secretKeyMaterial: ArrayBuffer,
|
|
467
|
+
/// parameterRange: ArrayBuffer|null) -> { channel_id, request_proto_bytes }`
|
|
420
468
|
jsi::Value extractPairRequest(jsi::Runtime& rt,
|
|
421
469
|
const jsi::Value&,
|
|
422
470
|
const jsi::Value* args,
|
|
423
471
|
size_t count) {
|
|
424
|
-
requireArgs(rt, "extract_pair_request", count,
|
|
472
|
+
requireArgs(rt, "extract_pair_request", count, 3);
|
|
425
473
|
ByteView request = asBytes(rt, args[0]);
|
|
426
474
|
ByteView secretKeyMaterial = asBytes(rt, args[1]);
|
|
475
|
+
ByteView parameterRange = asBytes(rt, args[2]);
|
|
427
476
|
ExtractPairRequestResult result = extract_pair_request(
|
|
428
|
-
request.ptr, request.len, secretKeyMaterial.ptr, secretKeyMaterial.len
|
|
477
|
+
request.ptr, request.len, secretKeyMaterial.ptr, secretKeyMaterial.len,
|
|
478
|
+
parameterRange.ptr, parameterRange.len);
|
|
429
479
|
if (result.error.code != 0) {
|
|
430
480
|
throwDeRecError(rt, result.error);
|
|
431
481
|
}
|
|
@@ -506,19 +556,21 @@ jsi::Value extractPairResponse(jsi::Runtime& rt,
|
|
|
506
556
|
}
|
|
507
557
|
|
|
508
558
|
/// `process_pair_response_message(contactMessage: ArrayBuffer, responseProto:
|
|
509
|
-
/// ArrayBuffer, secretKeyMaterial: ArrayBuffer
|
|
510
|
-
/// channel_id }`
|
|
559
|
+
/// ArrayBuffer, secretKeyMaterial: ArrayBuffer, parameterRange:
|
|
560
|
+
/// ArrayBuffer|null) -> { shared_key, channel_id }`
|
|
511
561
|
jsi::Value processPairResponseMessage(jsi::Runtime& rt,
|
|
512
562
|
const jsi::Value&,
|
|
513
563
|
const jsi::Value* args,
|
|
514
564
|
size_t count) {
|
|
515
|
-
requireArgs(rt, "process_pair_response_message", count,
|
|
565
|
+
requireArgs(rt, "process_pair_response_message", count, 4);
|
|
516
566
|
ByteView contactMessage = asBytes(rt, args[0]);
|
|
517
567
|
ByteView responseProto = asBytes(rt, args[1]);
|
|
518
568
|
ByteView secretKeyMaterial = asBytes(rt, args[2]);
|
|
569
|
+
ByteView parameterRange = asBytes(rt, args[3]);
|
|
519
570
|
ProcessPairResponseMessageResult result = process_pair_response_message(
|
|
520
571
|
contactMessage.ptr, contactMessage.len, responseProto.ptr,
|
|
521
|
-
responseProto.len, secretKeyMaterial.ptr, secretKeyMaterial.len
|
|
572
|
+
responseProto.len, secretKeyMaterial.ptr, secretKeyMaterial.len,
|
|
573
|
+
parameterRange.ptr, parameterRange.len);
|
|
522
574
|
if (result.error.code != 0) {
|
|
523
575
|
throwDeRecError(rt, result.error);
|
|
524
576
|
}
|
|
@@ -645,6 +697,79 @@ jsi::Value processPrePairResponseMessage(jsi::Runtime& rt,
|
|
|
645
697
|
return jsi::Value(rt, payload);
|
|
646
698
|
}
|
|
647
699
|
|
|
700
|
+
/// `produce_pre_pair_no_keys_response_message(channelId: bigint,
|
|
701
|
+
/// requestProto: ArrayBuffer) -> { envelope_wire_bytes,
|
|
702
|
+
/// secret_key_material }`
|
|
703
|
+
jsi::Value producePrePairNoKeysResponseMessage(jsi::Runtime& rt,
|
|
704
|
+
const jsi::Value&,
|
|
705
|
+
const jsi::Value* args,
|
|
706
|
+
size_t count) {
|
|
707
|
+
requireArgs(rt, "produce_pre_pair_no_keys_response_message", count, 2);
|
|
708
|
+
uint64_t channelId = asU64(rt, args[0]);
|
|
709
|
+
ByteView requestProto = asBytes(rt, args[1]);
|
|
710
|
+
ProducePrePairNoKeysResponseMessageResult result =
|
|
711
|
+
produce_pre_pair_no_keys_response_message(channelId, requestProto.ptr,
|
|
712
|
+
requestProto.len);
|
|
713
|
+
if (result.error.code != 0) {
|
|
714
|
+
throwDeRecError(rt, result.error);
|
|
715
|
+
}
|
|
716
|
+
std::vector<uint8_t> envelope = takeBuffer(result.envelope_wire_bytes);
|
|
717
|
+
std::vector<uint8_t> secretKeyMaterial = takeBuffer(result.secret_key_material);
|
|
718
|
+
auto payload = jsi::Object(rt);
|
|
719
|
+
payload.setProperty(rt, "envelope_wire_bytes",
|
|
720
|
+
toArrayBuffer(rt, envelope.data(), envelope.size()));
|
|
721
|
+
payload.setProperty(
|
|
722
|
+
rt, "secret_key_material",
|
|
723
|
+
toArrayBuffer(rt, secretKeyMaterial.data(), secretKeyMaterial.size()));
|
|
724
|
+
return jsi::Value(rt, payload);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/// `process_pre_pair_no_keys_response_message(contactMessage: ArrayBuffer,
|
|
728
|
+
/// responseProto: ArrayBuffer) -> { mlkem_encapsulation_key,
|
|
729
|
+
/// ecies_public_key, nonce }`
|
|
730
|
+
jsi::Value processPrePairNoKeysResponseMessage(jsi::Runtime& rt,
|
|
731
|
+
const jsi::Value&,
|
|
732
|
+
const jsi::Value* args,
|
|
733
|
+
size_t count) {
|
|
734
|
+
requireArgs(rt, "process_pre_pair_no_keys_response_message", count, 2);
|
|
735
|
+
ByteView contactMessage = asBytes(rt, args[0]);
|
|
736
|
+
ByteView responseProto = asBytes(rt, args[1]);
|
|
737
|
+
ProcessPrePairResponseMessageResult result =
|
|
738
|
+
process_pre_pair_no_keys_response_message(contactMessage.ptr,
|
|
739
|
+
contactMessage.len,
|
|
740
|
+
responseProto.ptr,
|
|
741
|
+
responseProto.len);
|
|
742
|
+
if (result.error.code != 0) {
|
|
743
|
+
throwDeRecError(rt, result.error);
|
|
744
|
+
}
|
|
745
|
+
std::vector<uint8_t> mlkemKey = takeBuffer(result.mlkem_encapsulation_key);
|
|
746
|
+
std::vector<uint8_t> eciesKey = takeBuffer(result.ecies_public_key);
|
|
747
|
+
auto payload = jsi::Object(rt);
|
|
748
|
+
payload.setProperty(rt, "mlkem_encapsulation_key",
|
|
749
|
+
toArrayBuffer(rt, mlkemKey.data(), mlkemKey.size()));
|
|
750
|
+
payload.setProperty(rt, "ecies_public_key",
|
|
751
|
+
toArrayBuffer(rt, eciesKey.data(), eciesKey.size()));
|
|
752
|
+
payload.setProperty(rt, "nonce",
|
|
753
|
+
jsi::Value(rt, jsi::BigInt::fromUint64(rt, result.nonce)));
|
|
754
|
+
return jsi::Value(rt, payload);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/// `pairing_fingerprint(sharedKey: ArrayBuffer) -> string`
|
|
758
|
+
jsi::Value pairingFingerprint(jsi::Runtime& rt,
|
|
759
|
+
const jsi::Value&,
|
|
760
|
+
const jsi::Value* args,
|
|
761
|
+
size_t count) {
|
|
762
|
+
requireArgs(rt, "pairing_fingerprint", count, 1);
|
|
763
|
+
ByteView sharedKey = asBytes(rt, args[0]);
|
|
764
|
+
PairingFingerprintResult result =
|
|
765
|
+
pairing_fingerprint(sharedKey.ptr, sharedKey.len);
|
|
766
|
+
if (result.error.code != 0) {
|
|
767
|
+
throwDeRecError(rt, result.error);
|
|
768
|
+
}
|
|
769
|
+
std::string fingerprint = takeString(result.fingerprint);
|
|
770
|
+
return jsi::String::createFromUtf8(rt, fingerprint);
|
|
771
|
+
}
|
|
772
|
+
|
|
648
773
|
/// `produce_get_share_request_message(channelId: bigint, secretId: bigint,
|
|
649
774
|
/// version: number, sharedKey: ArrayBuffer, replyTo: ArrayBuffer|null)
|
|
650
775
|
/// -> ArrayBuffer`
|
|
@@ -1134,6 +1259,8 @@ void bind(jsi::Runtime& rt,
|
|
|
1134
1259
|
} // namespace
|
|
1135
1260
|
|
|
1136
1261
|
void installPrimitives(jsi::Runtime& rt, jsi::Object& host) {
|
|
1262
|
+
bind(rt, host, "version", 0, protocolVersion);
|
|
1263
|
+
bind(rt, host, "generate_replica_id", 0, generateReplicaId);
|
|
1137
1264
|
bind(rt, host, "produce_get_secret_ids_versions_request_message", 3,
|
|
1138
1265
|
produceGetSecretIdsVersionsRequestMessage);
|
|
1139
1266
|
bind(rt, host, "extract_get_secret_ids_versions_request", 2,
|
|
@@ -1147,16 +1274,17 @@ void installPrimitives(jsi::Runtime& rt, jsi::Object& host) {
|
|
|
1147
1274
|
bind(rt, host, "apply_trace_id_to_envelope", 2, applyTraceId);
|
|
1148
1275
|
bind(rt, host, "read_trace_id_from_envelope", 1, readTraceId);
|
|
1149
1276
|
bind(rt, host, "create_contact_message", 5, createContactMessage);
|
|
1150
|
-
bind(rt, host, "validate_contact_message", 1, validateContactMessage);
|
|
1151
1277
|
bind(rt, host, "encode_contact_message", 1, encodeContactMessage);
|
|
1152
1278
|
bind(rt, host, "decode_contact_message", 1, decodeContactMessage);
|
|
1279
|
+
bind(rt, host, "transport_protocol_discriminant", 1,
|
|
1280
|
+
transportProtocolDiscriminant);
|
|
1153
1281
|
bind(rt, host, "decode_message_json", 2, decodeMessageJson);
|
|
1154
1282
|
bind(rt, host, "encode_message_json", 2, encodeMessageJson);
|
|
1155
1283
|
bind(rt, host, "produce_pair_request_message", 5, producePairRequestMessage);
|
|
1156
|
-
bind(rt, host, "extract_pair_request",
|
|
1284
|
+
bind(rt, host, "extract_pair_request", 3, extractPairRequest);
|
|
1157
1285
|
bind(rt, host, "produce_pair_response_message", 7, producePairResponseMessage);
|
|
1158
1286
|
bind(rt, host, "extract_pair_response", 2, extractPairResponse);
|
|
1159
|
-
bind(rt, host, "process_pair_response_message",
|
|
1287
|
+
bind(rt, host, "process_pair_response_message", 4, processPairResponseMessage);
|
|
1160
1288
|
bind(rt, host, "produce_pre_pair_request_message", 2,
|
|
1161
1289
|
producePrePairRequestMessage);
|
|
1162
1290
|
bind(rt, host, "extract_pre_pair_request", 1, extractPrePairRequest);
|
|
@@ -1165,6 +1293,11 @@ void installPrimitives(jsi::Runtime& rt, jsi::Object& host) {
|
|
|
1165
1293
|
bind(rt, host, "extract_pre_pair_response", 1, extractPrePairResponse);
|
|
1166
1294
|
bind(rt, host, "process_pre_pair_response_message", 2,
|
|
1167
1295
|
processPrePairResponseMessage);
|
|
1296
|
+
bind(rt, host, "produce_pre_pair_no_keys_response_message", 2,
|
|
1297
|
+
producePrePairNoKeysResponseMessage);
|
|
1298
|
+
bind(rt, host, "process_pre_pair_no_keys_response_message", 2,
|
|
1299
|
+
processPrePairNoKeysResponseMessage);
|
|
1300
|
+
bind(rt, host, "pairing_fingerprint", 1, pairingFingerprint);
|
|
1168
1301
|
bind(rt, host, "produce_get_share_request_message", 5,
|
|
1169
1302
|
produceGetShareRequestMessage);
|
|
1170
1303
|
bind(rt, host, "extract_get_share_request", 2, extractGetShareRequest);
|
package/cpp/Primitives.h
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
#include <jsi/jsi.h>
|
|
7
7
|
|
|
8
|
+
#include <vector>
|
|
9
|
+
|
|
8
10
|
extern "C" {
|
|
9
11
|
#include "derec_ffi.h"
|
|
10
12
|
}
|
|
@@ -16,6 +18,13 @@ namespace derec {
|
|
|
16
18
|
/// application error handling ports between the JavaScript SDKs unchanged.
|
|
17
19
|
[[noreturn]] void throwDeRecError(facebook::jsi::Runtime& rt, const DeRecError& error);
|
|
18
20
|
|
|
21
|
+
/// As above, additionally attaching `channel_ids` — the JSON array of
|
|
22
|
+
/// decimal-string ids in `conflictingChannelIdsJson`, verbatim — when that
|
|
23
|
+
/// buffer is non-empty. Only `restore` produces one.
|
|
24
|
+
[[noreturn]] void throwDeRecError(facebook::jsi::Runtime& rt,
|
|
25
|
+
const DeRecError& error,
|
|
26
|
+
const std::vector<uint8_t>& conflictingChannelIdsJson);
|
|
27
|
+
|
|
19
28
|
/// Read an `ArrayBuffer` or typed-array argument as a byte range.
|
|
20
29
|
/// Returns `{nullptr, 0}` for `null`/`undefined`.
|
|
21
30
|
struct ByteView {
|
|
@@ -30,6 +39,13 @@ facebook::jsi::Value toArrayBuffer(facebook::jsi::Runtime& rt,
|
|
|
30
39
|
size_t len);
|
|
31
40
|
|
|
32
41
|
/// Read a `bigint` or `number` argument as a `uint64_t`.
|
|
42
|
+
/// A genuine `Uint8Array` holding a copy of `bytes`.
|
|
43
|
+
facebook::jsi::Value toUint8ArrayVal(facebook::jsi::Runtime& rt,
|
|
44
|
+
const uint8_t* bytes,
|
|
45
|
+
size_t len);
|
|
46
|
+
facebook::jsi::Value toUint8ArrayVal(facebook::jsi::Runtime& rt,
|
|
47
|
+
const std::vector<uint8_t>& bytes);
|
|
48
|
+
|
|
33
49
|
uint64_t asU64(facebook::jsi::Runtime& rt, const facebook::jsi::Value& value);
|
|
34
50
|
|
|
35
51
|
/// Install every stateless FFI primitive as a host function on `host`, named
|