librats 2.2.0 → 2.3.1
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/lib/index.d.ts +30 -2
- package/lib/index.js +16 -1
- package/native-src/CMakeLists.txt +45 -3
- package/native-src/src/librats/bindings/rats.cpp +56 -3
- package/native-src/src/librats/bindings/rats.h +62 -1
- package/native-src/src/librats/core/types.cpp +1 -1
- package/native-src/src/librats/core/types.h +9 -6
- package/native-src/src/librats/node/config.h +9 -2
- package/native-src/src/librats/node/node.cpp +20 -5
- package/native-src/src/librats/node/node.h +26 -6
- package/native-src/src/librats/node/peer_network.h +19 -2
- package/native-src/src/librats/peer/peer.h +17 -6
- package/native-src/src/librats/security/noise_security.cpp +2 -0
- package/native-src/src/librats/security/plaintext_security.h +1 -0
- package/native-src/src/librats/security/session.h +7 -0
- package/native-src/src/librats/storage/storage.cpp +434 -213
- package/native-src/src/librats/storage/storage.h +163 -19
- package/native-src/src/librats/subsystems/file_transfer.cpp +1 -1
- package/native-src/src/librats/subsystems/pubsub.cpp +1 -1
- package/native-src/src/librats/subsystems/reconnection.cpp +1 -1
- package/native-src/src/librats/subsystems/relay.cpp +2 -1
- package/native-src/src/librats/transport/connection.cpp +40 -8
- package/native-src/src/librats/transport/connection.h +12 -2
- package/native-src/src/librats/util/logger.h +37 -5
- package/native-src/src/librats/util/network_monitor.cpp +14 -0
- package/native-src/src/librats/util/network_utils.cpp +10 -1
- package/package.json +1 -1
- package/src/librats_node.cpp +50 -1
package/lib/index.d.ts
CHANGED
|
@@ -91,9 +91,23 @@ declare module 'librats' {
|
|
|
91
91
|
preferredTransport?: TransportValue;
|
|
92
92
|
/** Delay before the other transport is raced alongside; 0 disables. Default 1200. */
|
|
93
93
|
transportFallbackMs?: number;
|
|
94
|
+
/**
|
|
95
|
+
* Bytes a peer's send queue may hold before an app that keeps sending anyway
|
|
96
|
+
* has that peer dropped with reason `RATS_CLOSE_SLOW_CONSUMER`. 0 (default)
|
|
97
|
+
* uses the library's 8 MiB. A quarter of it is where `peerWritable()` starts
|
|
98
|
+
* returning false. Not a maximum message size.
|
|
99
|
+
*/
|
|
100
|
+
sendQueueLimit?: number;
|
|
94
101
|
}
|
|
95
102
|
|
|
96
103
|
export type PeerHandler = (peerId: string) => void;
|
|
104
|
+
/**
|
|
105
|
+
* `reason` is why the peer went — "RATS_CLOSE_PEER_CLOSED",
|
|
106
|
+
* "RATS_CLOSE_SLOW_CONSUMER" and so on. Worth branching on: a peer that left is
|
|
107
|
+
* one to redial, while SLOW_CONSUMER means *you* were sending faster than the
|
|
108
|
+
* link drained, and redialing that one just repeats the overload.
|
|
109
|
+
*/
|
|
110
|
+
export type PeerDisconnectHandler = (peerId: string, reason: string) => void;
|
|
97
111
|
export type MessageHandler = (peerId: string, data: Buffer) => void;
|
|
98
112
|
export type TopicHandler = (peerId: string, topic: string, data: Buffer) => void;
|
|
99
113
|
export type JsonHandler = (peerId: string, value: any) => void;
|
|
@@ -180,17 +194,31 @@ declare module 'librats' {
|
|
|
180
194
|
|
|
181
195
|
// ---- raw channel messaging ----
|
|
182
196
|
|
|
183
|
-
/**
|
|
197
|
+
/**
|
|
198
|
+
* Send raw bytes on a named channel to one peer. Returning means the message
|
|
199
|
+
* was queued, never that it arrived — if you send in bulk, follow it with
|
|
200
|
+
* `peerWritable()`.
|
|
201
|
+
*/
|
|
184
202
|
send(peerId: string, channel: string, data: string | Buffer): void;
|
|
185
203
|
/** Broadcast raw bytes on a named channel to every connected peer. */
|
|
186
204
|
broadcast(channel: string, data: string | Buffer): void;
|
|
205
|
+
/**
|
|
206
|
+
* Whether this peer's send queue still has room (false also when it is not
|
|
207
|
+
* connected). False means stop: what you just sent was queued like anything
|
|
208
|
+
* else and nothing was dropped, but keep piling on and the peer is dropped
|
|
209
|
+
* with reason `RATS_CLOSE_SLOW_CONSUMER`. Wait for `onPeerWritable`, or poll
|
|
210
|
+
* this. Not a size limit — one message of any size is always queued.
|
|
211
|
+
*/
|
|
212
|
+
peerWritable(peerId: string): boolean;
|
|
187
213
|
/** Register a handler for a named channel. Additive; register before `start()`. */
|
|
188
214
|
on(channel: string, handler: MessageHandler): void;
|
|
189
215
|
|
|
190
216
|
// ---- peer events (register before start) ----
|
|
191
217
|
|
|
192
218
|
onPeerConnected(handler: PeerHandler): void;
|
|
193
|
-
onPeerDisconnected(handler:
|
|
219
|
+
onPeerDisconnected(handler: PeerDisconnectHandler): void;
|
|
220
|
+
/** A peer whose queue had filled has drained back under its mark. */
|
|
221
|
+
onPeerWritable(handler: PeerHandler): void;
|
|
194
222
|
|
|
195
223
|
// ---- discovery (enable before start) ----
|
|
196
224
|
|
package/lib/index.js
CHANGED
|
@@ -193,6 +193,14 @@ class RatsNode {
|
|
|
193
193
|
*/
|
|
194
194
|
broadcast(channel, data) { this._native.broadcast(channel, data); }
|
|
195
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Whether this peer's send queue still has room. False means pause and wait for
|
|
198
|
+
* `onPeerWritable` — keep sending regardless and the peer is dropped with
|
|
199
|
+
* reason `RATS_CLOSE_SLOW_CONSUMER`.
|
|
200
|
+
* @param {string} peerId @returns {boolean}
|
|
201
|
+
*/
|
|
202
|
+
peerWritable(peerId) { return this._native.peerWritable(peerId); }
|
|
203
|
+
|
|
196
204
|
/**
|
|
197
205
|
* Register a handler for a named channel. Additive; register before `start()`.
|
|
198
206
|
* @param {string} channel
|
|
@@ -205,9 +213,16 @@ class RatsNode {
|
|
|
205
213
|
/** @param {(peerId: string) => void} handler fired when a peer connects. */
|
|
206
214
|
onPeerConnected(handler) { this._native.onPeerConnected(handler); }
|
|
207
215
|
|
|
208
|
-
/**
|
|
216
|
+
/**
|
|
217
|
+
* @param {(peerId: string, reason: string) => void} handler fired when a peer
|
|
218
|
+
* disconnects. `reason` is a name like "RATS_CLOSE_SLOW_CONSUMER", which means
|
|
219
|
+
* this node was sending faster than the link drained.
|
|
220
|
+
*/
|
|
209
221
|
onPeerDisconnected(handler) { this._native.onPeerDisconnected(handler); }
|
|
210
222
|
|
|
223
|
+
/** @param {(peerId: string) => void} handler fired when a full queue drained. */
|
|
224
|
+
onPeerWritable(handler) { this._native.onPeerWritable(handler); }
|
|
225
|
+
|
|
211
226
|
// ---- discovery (enable before start) ----
|
|
212
227
|
|
|
213
228
|
/**
|
|
@@ -492,16 +492,58 @@ if(WIN32)
|
|
|
492
492
|
target_link_libraries(rats ws2_32 iphlpapi bcrypt advapi32)
|
|
493
493
|
endif()
|
|
494
494
|
|
|
495
|
-
|
|
495
|
+
# Android can be built two ways, and only one of them sets CMake's ANDROID variable:
|
|
496
|
+
# through the NDK's android.toolchain.cmake (sets ANDROID / ANDROID_PLATFORM), or by
|
|
497
|
+
# simply pointing CC/CXX at the NDK's <triple><api>-clang wrappers, which is what the
|
|
498
|
+
# Android CI does. The compiler is the only thing that knows in both cases, so ask it
|
|
499
|
+
# rather than trusting a toolchain-file-only variable.
|
|
500
|
+
if(NOT ANDROID)
|
|
501
|
+
check_cxx_source_compiles("
|
|
502
|
+
#ifndef __ANDROID__
|
|
503
|
+
#error not android
|
|
504
|
+
#endif
|
|
505
|
+
int main() { return 0; }
|
|
506
|
+
" RATS_TARGET_IS_ANDROID)
|
|
507
|
+
else()
|
|
508
|
+
set(RATS_TARGET_IS_ANDROID TRUE)
|
|
509
|
+
endif()
|
|
510
|
+
|
|
511
|
+
if(RATS_TARGET_IS_ANDROID)
|
|
512
|
+
# The logger routes console output through __android_log_print, because an app's
|
|
513
|
+
# stdout/stderr go nowhere on Android. This is used from librats/util/logger.h,
|
|
514
|
+
# i.e. from consumers of the static library too, so the dependency is PUBLIC.
|
|
515
|
+
# Only search for it when the NDK toolchain file configured a sysroot to search;
|
|
516
|
+
# with a bare NDK clang, find_library would look at host paths, so let the driver
|
|
517
|
+
# resolve -llog out of its own sysroot instead.
|
|
518
|
+
if(ANDROID)
|
|
519
|
+
find_library(log-lib log)
|
|
520
|
+
target_link_libraries(rats ${log-lib})
|
|
521
|
+
else()
|
|
522
|
+
target_link_libraries(rats log)
|
|
523
|
+
endif()
|
|
524
|
+
|
|
525
|
+
# API level: from the toolchain file when it set one, otherwise from the compiler.
|
|
496
526
|
if(DEFINED ANDROID_PLATFORM)
|
|
497
527
|
string(REGEX REPLACE "android-" "" ANDROID_API_LEVEL ${ANDROID_PLATFORM})
|
|
498
528
|
math(EXPR ANDROID_API_LEVEL "${ANDROID_API_LEVEL}")
|
|
499
529
|
message(STATUS "Android API level detected: ${ANDROID_API_LEVEL}")
|
|
530
|
+
if(ANDROID_API_LEVEL LESS 24)
|
|
531
|
+
set(RATS_ANDROID_NEEDS_IFADDRS TRUE)
|
|
532
|
+
endif()
|
|
500
533
|
else()
|
|
501
|
-
|
|
534
|
+
check_cxx_source_compiles("
|
|
535
|
+
#include <android/api-level.h>
|
|
536
|
+
#if __ANDROID_API__ < 24
|
|
537
|
+
#error too old
|
|
538
|
+
#endif
|
|
539
|
+
int main() { return 0; }
|
|
540
|
+
" RATS_ANDROID_API_24_OR_NEWER)
|
|
541
|
+
if(NOT RATS_ANDROID_API_24_OR_NEWER)
|
|
542
|
+
set(RATS_ANDROID_NEEDS_IFADDRS TRUE)
|
|
543
|
+
endif()
|
|
502
544
|
endif()
|
|
503
545
|
|
|
504
|
-
if(
|
|
546
|
+
if(RATS_ANDROID_NEEDS_IFADDRS)
|
|
505
547
|
target_sources(rats PRIVATE ${PROJECT_SOURCE_DIR}/3rdparty/android/ifaddrs-android.c)
|
|
506
548
|
target_include_directories(rats
|
|
507
549
|
PRIVATE
|
|
@@ -85,6 +85,24 @@ char* dup_string(const std::string& s) {
|
|
|
85
85
|
return out;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
rats_close_reason_t to_c_reason(CloseReason r) {
|
|
89
|
+
switch (r) {
|
|
90
|
+
case CloseReason::LocalClose: return RATS_CLOSE_LOCAL;
|
|
91
|
+
case CloseReason::PeerClosed: return RATS_CLOSE_PEER_CLOSED;
|
|
92
|
+
case CloseReason::PeerReset: return RATS_CLOSE_PEER_RESET;
|
|
93
|
+
case CloseReason::ConnectFailed: return RATS_CLOSE_CONNECT_FAILED;
|
|
94
|
+
case CloseReason::HandshakeFailed: return RATS_CLOSE_HANDSHAKE_FAILED;
|
|
95
|
+
case CloseReason::ProtocolError: return RATS_CLOSE_PROTOCOL_ERROR;
|
|
96
|
+
case CloseReason::SlowConsumer: return RATS_CLOSE_SLOW_CONSUMER;
|
|
97
|
+
case CloseReason::ReactorShutdown: return RATS_CLOSE_SHUTDOWN;
|
|
98
|
+
case CloseReason::DuplicateConn: return RATS_CLOSE_DUPLICATE;
|
|
99
|
+
case CloseReason::PeerLimit: return RATS_CLOSE_PEER_LIMIT;
|
|
100
|
+
case CloseReason::IdleTimeout: return RATS_CLOSE_IDLE_TIMEOUT;
|
|
101
|
+
case CloseReason::DialSuperseded: return RATS_CLOSE_DIAL_SUPERSEDED;
|
|
102
|
+
}
|
|
103
|
+
return RATS_CLOSE_PEER_CLOSED;
|
|
104
|
+
}
|
|
105
|
+
|
|
88
106
|
} // namespace
|
|
89
107
|
|
|
90
108
|
extern "C" {
|
|
@@ -103,6 +121,24 @@ const char* rats_error_str(rats_error_t err) {
|
|
|
103
121
|
return "RATS_ERR_UNKNOWN";
|
|
104
122
|
}
|
|
105
123
|
|
|
124
|
+
const char* rats_close_reason_str(rats_close_reason_t reason) {
|
|
125
|
+
switch (reason) {
|
|
126
|
+
case RATS_CLOSE_LOCAL: return "RATS_CLOSE_LOCAL";
|
|
127
|
+
case RATS_CLOSE_PEER_CLOSED: return "RATS_CLOSE_PEER_CLOSED";
|
|
128
|
+
case RATS_CLOSE_PEER_RESET: return "RATS_CLOSE_PEER_RESET";
|
|
129
|
+
case RATS_CLOSE_CONNECT_FAILED: return "RATS_CLOSE_CONNECT_FAILED";
|
|
130
|
+
case RATS_CLOSE_HANDSHAKE_FAILED: return "RATS_CLOSE_HANDSHAKE_FAILED";
|
|
131
|
+
case RATS_CLOSE_PROTOCOL_ERROR: return "RATS_CLOSE_PROTOCOL_ERROR";
|
|
132
|
+
case RATS_CLOSE_SLOW_CONSUMER: return "RATS_CLOSE_SLOW_CONSUMER";
|
|
133
|
+
case RATS_CLOSE_SHUTDOWN: return "RATS_CLOSE_SHUTDOWN";
|
|
134
|
+
case RATS_CLOSE_DUPLICATE: return "RATS_CLOSE_DUPLICATE";
|
|
135
|
+
case RATS_CLOSE_PEER_LIMIT: return "RATS_CLOSE_PEER_LIMIT";
|
|
136
|
+
case RATS_CLOSE_IDLE_TIMEOUT: return "RATS_CLOSE_IDLE_TIMEOUT";
|
|
137
|
+
case RATS_CLOSE_DIAL_SUPERSEDED: return "RATS_CLOSE_DIAL_SUPERSEDED";
|
|
138
|
+
}
|
|
139
|
+
return "RATS_CLOSE_UNKNOWN";
|
|
140
|
+
}
|
|
141
|
+
|
|
106
142
|
/* — construction / lifecycle — */
|
|
107
143
|
|
|
108
144
|
rats_config_t rats_config_default(void) {
|
|
@@ -118,6 +154,7 @@ rats_config_t rats_config_default(void) {
|
|
|
118
154
|
c.enable_udp = 1;
|
|
119
155
|
c.preferred_transport = RATS_TRANSPORT_UDP;
|
|
120
156
|
c.transport_fallback_ms = 1200;
|
|
157
|
+
c.send_queue_limit = 0;
|
|
121
158
|
return c;
|
|
122
159
|
}
|
|
123
160
|
|
|
@@ -153,6 +190,7 @@ rats_t rats_create_config(const rats_config_t* cfg) {
|
|
|
153
190
|
? TransportKind::Tcp
|
|
154
191
|
: TransportKind::Udp;
|
|
155
192
|
config.transport_fallback_ms = cfg->transport_fallback_ms;
|
|
193
|
+
config.send_queue_limit = cfg->send_queue_limit;
|
|
156
194
|
}
|
|
157
195
|
return make_handle(std::move(config));
|
|
158
196
|
}
|
|
@@ -224,6 +262,13 @@ rats_error_t rats_send(rats_t node, const char* peer_id_hex,
|
|
|
224
262
|
return RATS_OK;
|
|
225
263
|
}
|
|
226
264
|
|
|
265
|
+
int rats_peer_writable(rats_t node, const char* peer_id_hex) {
|
|
266
|
+
if (!peer_id_hex) return 0;
|
|
267
|
+
auto id = PeerId::from_hex(peer_id_hex);
|
|
268
|
+
if (!id) return 0;
|
|
269
|
+
return node_of(node)->peer_writable(*id) ? 1 : 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
227
272
|
rats_error_t rats_broadcast(rats_t node, const char* channel, const void* data, size_t len) {
|
|
228
273
|
if (!channel) return RATS_ERR_INVALID_ARG;
|
|
229
274
|
node_of(node)->broadcast(channel, ByteView(static_cast<const uint8_t*>(data), len));
|
|
@@ -238,10 +283,18 @@ rats_error_t rats_on_peer_connected(rats_t node, rats_peer_cb cb, void* user) {
|
|
|
238
283
|
return RATS_OK;
|
|
239
284
|
}
|
|
240
285
|
|
|
241
|
-
rats_error_t rats_on_peer_disconnected(rats_t node,
|
|
286
|
+
rats_error_t rats_on_peer_disconnected(rats_t node, rats_peer_disconnect_cb cb, void* user) {
|
|
287
|
+
if (!cb) return RATS_ERR_INVALID_ARG;
|
|
288
|
+
node_of(node)->on_peer_disconnected([cb, user](const PeerId& id, CloseReason reason) {
|
|
289
|
+
cb(user, id.to_hex().c_str(), to_c_reason(reason));
|
|
290
|
+
});
|
|
291
|
+
return RATS_OK;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
rats_error_t rats_on_peer_writable(rats_t node, rats_peer_cb cb, void* user) {
|
|
242
295
|
if (!cb) return RATS_ERR_INVALID_ARG;
|
|
243
|
-
node_of(node)->
|
|
244
|
-
cb(user, id.to_hex().c_str());
|
|
296
|
+
node_of(node)->on_peer_writable([cb, user](const Peer& peer) {
|
|
297
|
+
cb(user, peer.id().to_hex().c_str());
|
|
245
298
|
});
|
|
246
299
|
return RATS_OK;
|
|
247
300
|
}
|
|
@@ -53,6 +53,33 @@ typedef enum {
|
|
|
53
53
|
RATS_TRANSPORT_RELAY = 2
|
|
54
54
|
} rats_transport_t;
|
|
55
55
|
|
|
56
|
+
/* Why a peer connection ended, handed to rats_on_peer_disconnected(). The
|
|
57
|
+
* reason is what tells "I was sending too fast" apart from "the peer left" —
|
|
58
|
+
* without it both look the same and the usual answer to either is to reconnect
|
|
59
|
+
* and repeat whatever caused it. Use rats_close_reason_str() for a name. */
|
|
60
|
+
typedef enum {
|
|
61
|
+
RATS_CLOSE_LOCAL = 0, /* we asked to disconnect */
|
|
62
|
+
RATS_CLOSE_PEER_CLOSED = 1, /* the peer closed cleanly */
|
|
63
|
+
RATS_CLOSE_PEER_RESET = 2, /* connection reset / socket error */
|
|
64
|
+
RATS_CLOSE_CONNECT_FAILED = 3, /* the dial never completed */
|
|
65
|
+
RATS_CLOSE_HANDSHAKE_FAILED = 4, /* secure handshake failed or timed out */
|
|
66
|
+
RATS_CLOSE_PROTOCOL_ERROR = 5, /* malformed frame / decryption failure */
|
|
67
|
+
/* We kept sending while this peer's queue was already past its limit. THE
|
|
68
|
+
* ONE REASON THE APPLICATION CAN PREVENT: heed rats_send()'s answer and
|
|
69
|
+
* rats_peer_writable(), and wait for the writable callback. It does NOT mean
|
|
70
|
+
* a single message was too big — one message is always accepted whatever its
|
|
71
|
+
* size; only piling more on a full queue gets a peer dropped. */
|
|
72
|
+
RATS_CLOSE_SLOW_CONSUMER = 6,
|
|
73
|
+
RATS_CLOSE_SHUTDOWN = 7, /* the node is stopping */
|
|
74
|
+
RATS_CLOSE_DUPLICATE = 8, /* superseded by another link to the same peer */
|
|
75
|
+
RATS_CLOSE_PEER_LIMIT = 9, /* inbound refused: peer limit reached */
|
|
76
|
+
RATS_CLOSE_IDLE_TIMEOUT = 10, /* datagram link went silent */
|
|
77
|
+
RATS_CLOSE_DIAL_SUPERSEDED = 11 /* a racing dial over the other transport won */
|
|
78
|
+
} rats_close_reason_t;
|
|
79
|
+
|
|
80
|
+
/** Static human-readable name for a close reason (never NULL, never freed). */
|
|
81
|
+
RATS_API const char* rats_close_reason_str(rats_close_reason_t reason);
|
|
82
|
+
|
|
56
83
|
/* Bitmask of transports (see rats_transports / rats_peer_transports). */
|
|
57
84
|
#define RATS_TRANSPORT_MASK_TCP 0x1u
|
|
58
85
|
#define RATS_TRANSPORT_MASK_UDP 0x2u
|
|
@@ -102,6 +129,14 @@ typedef struct {
|
|
|
102
129
|
rats_transport_t preferred_transport; /* tried first when dialing (default UDP) */
|
|
103
130
|
uint32_t transport_fallback_ms; /* delay before racing the other transport;
|
|
104
131
|
* 0 = never fall back (default 1200) */
|
|
132
|
+
|
|
133
|
+
/* Bytes a peer's send queue may hold before an application that keeps sending
|
|
134
|
+
* anyway has that peer dropped with RATS_CLOSE_SLOW_CONSUMER. 0 = the library
|
|
135
|
+
* default (8 MiB). A quarter of it is where rats_peer_writable() starts
|
|
136
|
+
* answering 0, so lowering this makes an application feel backpressure sooner.
|
|
137
|
+
* It is NOT a maximum message size: one message is always queued whatever its
|
|
138
|
+
* size. */
|
|
139
|
+
size_t send_queue_limit;
|
|
105
140
|
} rats_config_t;
|
|
106
141
|
|
|
107
142
|
/** A config pre-filled with the library defaults (listening, Noise, ephemeral
|
|
@@ -153,18 +188,44 @@ RATS_API size_t rats_max_peers(rats_t node);
|
|
|
153
188
|
|
|
154
189
|
/* — messaging (named application channel, raw bytes) — */
|
|
155
190
|
|
|
191
|
+
/** Queue bytes for one peer on a named channel. RATS_OK means accepted and
|
|
192
|
+
* queued, never that it arrived. Pair it with rats_peer_writable() below if you
|
|
193
|
+
* send in bulk — that is the only way to learn you are outrunning the link
|
|
194
|
+
* before the peer is dropped for it. */
|
|
156
195
|
RATS_API rats_error_t rats_send(rats_t node, const char* peer_id_hex,
|
|
157
196
|
const char* channel, const void* data, size_t len);
|
|
158
197
|
RATS_API rats_error_t rats_broadcast(rats_t node, const char* channel,
|
|
159
198
|
const void* data, size_t len);
|
|
160
199
|
|
|
200
|
+
/** Whether this peer's send queue still has room — the same question rats_send()
|
|
201
|
+
* answers, asked without sending. Returns 1 for room, 0 for none (and 0 for a
|
|
202
|
+
* peer that is not connected).
|
|
203
|
+
*
|
|
204
|
+
* A 0 says "stop". The message you just sent was queued like any other and
|
|
205
|
+
* nothing was dropped, but keep piling on and this peer is dropped with
|
|
206
|
+
* RATS_CLOSE_SLOW_CONSUMER. Wait for the callback registered with
|
|
207
|
+
* rats_on_peer_writable(), or poll this from a thread of your own.
|
|
208
|
+
*
|
|
209
|
+
* Ask it right after rats_send(): the bytes you just handed over are already
|
|
210
|
+
* counted, so the answer covers your own send and not just what the reactor has
|
|
211
|
+
* got round to. It is NOT a size limit — a single message of any size is always
|
|
212
|
+
* accepted; only sending more on top of a full queue drops a peer. */
|
|
213
|
+
RATS_API int rats_peer_writable(rats_t node, const char* peer_id_hex);
|
|
214
|
+
|
|
161
215
|
/* — callbacks (register before start; invoked on a reactor thread) — */
|
|
162
216
|
|
|
163
217
|
typedef void (*rats_peer_cb)(void* user, const char* peer_id_hex);
|
|
218
|
+
typedef void (*rats_peer_disconnect_cb)(void* user, const char* peer_id_hex,
|
|
219
|
+
rats_close_reason_t reason);
|
|
164
220
|
typedef void (*rats_message_cb)(void* user, const char* peer_id_hex, const void* data, size_t len);
|
|
165
221
|
|
|
166
222
|
RATS_API rats_error_t rats_on_peer_connected(rats_t node, rats_peer_cb cb, void* user);
|
|
167
|
-
RATS_API rats_error_t rats_on_peer_disconnected(rats_t node,
|
|
223
|
+
RATS_API rats_error_t rats_on_peer_disconnected(rats_t node, rats_peer_disconnect_cb cb,
|
|
224
|
+
void* user);
|
|
225
|
+
/** "This peer can be written to again" — fired when a peer whose queue had filled
|
|
226
|
+
* past its mark has drained back under it. The other half of rats_send()
|
|
227
|
+
* reporting 0; an application that never asks never needs this. */
|
|
228
|
+
RATS_API rats_error_t rats_on_peer_writable(rats_t node, rats_peer_cb cb, void* user);
|
|
168
229
|
RATS_API rats_error_t rats_on(rats_t node, const char* channel, rats_message_cb cb, void* user);
|
|
169
230
|
|
|
170
231
|
/* — optional subsystems (enable before start) — */
|
|
@@ -13,7 +13,7 @@ const char* to_string(ConnState s) noexcept {
|
|
|
13
13
|
return "?";
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const char* to_string(CloseReason r) noexcept {
|
|
16
|
+
RATS_API const char* to_string(CloseReason r) noexcept {
|
|
17
17
|
switch (r) {
|
|
18
18
|
case CloseReason::LocalClose: return "LocalClose";
|
|
19
19
|
case CloseReason::PeerClosed: return "PeerClosed";
|
|
@@ -84,7 +84,9 @@ enum class CloseReason {
|
|
|
84
84
|
ConnectFailed, ///< Outbound transport connect never completed.
|
|
85
85
|
HandshakeFailed, ///< Secure-channel handshake failed or timed out.
|
|
86
86
|
ProtocolError, ///< Malformed frame / decryption failure on the wire.
|
|
87
|
-
SlowConsumer, ///<
|
|
87
|
+
SlowConsumer, ///< Kept sending with the queue already past its high-water
|
|
88
|
+
///< mark. Not "a message was too large": one message is
|
|
89
|
+
///< always queued whatever its size (Connection::send).
|
|
88
90
|
ReactorShutdown, ///< Reactor is stopping.
|
|
89
91
|
DuplicateConn, ///< Redundant connection to a peer we already hold; superseded.
|
|
90
92
|
PeerLimit, ///< Inbound rejected: the configured peer limit is reached.
|
|
@@ -93,11 +95,12 @@ enum class CloseReason {
|
|
|
93
95
|
};
|
|
94
96
|
|
|
95
97
|
const char* to_string(ConnState) noexcept;
|
|
96
|
-
|
|
97
|
-
///
|
|
98
|
-
///
|
|
99
|
-
|
|
100
|
-
///
|
|
98
|
+
/// Exported: a CloseReason is handed to every on_peer_disconnected handler, so a
|
|
99
|
+
/// consumer of the shared build has to be able to render one. (ConnState above is
|
|
100
|
+
/// not — it never leaves Connection.)
|
|
101
|
+
RATS_API const char* to_string(CloseReason) noexcept;
|
|
102
|
+
/// Exported for the same reason: TransportKind is part of the public surface
|
|
103
|
+
/// (NodeConfig::preferred_transport, PeerInfo::transport).
|
|
101
104
|
RATS_API const char* to_string(TransportKind) noexcept;
|
|
102
105
|
|
|
103
106
|
} // namespace librats
|
|
@@ -52,8 +52,15 @@ struct RATS_API NodeConfig {
|
|
|
52
52
|
/// connection. 0 disables the fallback: only the preferred transport is tried.
|
|
53
53
|
uint32_t transport_fallback_ms = 1200;
|
|
54
54
|
|
|
55
|
-
/// Bytes a peer's send queue may hold before
|
|
56
|
-
/// consumer. 0 uses the library
|
|
55
|
+
/// Bytes a peer's send queue may hold before an application that keeps
|
|
56
|
+
/// sending anyway has the peer dropped as a slow consumer. 0 uses the library
|
|
57
|
+
/// default (8 MiB).
|
|
58
|
+
///
|
|
59
|
+
/// It is not a maximum message size. A single message is always queued
|
|
60
|
+
/// whatever its size — a message cannot be sent by halves, and a healthy
|
|
61
|
+
/// connection must not die over one large frame — so the queue's real ceiling
|
|
62
|
+
/// is this plus one message. What gets a peer dropped is offering *another*
|
|
63
|
+
/// message while the queue is still over the mark.
|
|
57
64
|
///
|
|
58
65
|
/// A quarter of this is the mark at which send() starts answering "no room"
|
|
59
66
|
/// and on_peer_writable is what says the room is back — so lowering it makes
|
|
@@ -488,7 +488,15 @@ bool Node::send(const PeerId& to, std::string_view channel, ByteView payload) {
|
|
|
488
488
|
|
|
489
489
|
bool Node::peer_writable(const PeerId& id) const {
|
|
490
490
|
const auto dest = peers_.destination(id);
|
|
491
|
-
|
|
491
|
+
if (!dest) return false;
|
|
492
|
+
// Both halves of the backlog, exactly as send() weighs them: what the reactor
|
|
493
|
+
// has already queued (`writable`) *and* what a caller has handed over that the
|
|
494
|
+
// reactor has not taken up yet (`owed`). Reporting only the first would answer
|
|
495
|
+
// "there is room" to a caller that has just filled the queue itself and is
|
|
496
|
+
// waiting to hear otherwise — the queue it filled has not been looked at yet,
|
|
497
|
+
// so nothing about it has changed, and no event is coming either.
|
|
498
|
+
return dest->writable &&
|
|
499
|
+
dest->owed->load(std::memory_order_relaxed) <= send_low_water();
|
|
492
500
|
}
|
|
493
501
|
|
|
494
502
|
bool Node::broadcast(std::string_view channel, ByteView payload) {
|
|
@@ -682,7 +690,7 @@ void Node::on_closed(Connection& conn, CloseReason reason) {
|
|
|
682
690
|
// let a long-gone peer keep voting on how our NAT behaves.
|
|
683
691
|
nat_status_.forget(id);
|
|
684
692
|
LOG_INFO("node", "Peer " << id.short_hex() << " disconnected (" << to_string(reason) << ")");
|
|
685
|
-
for (auto& cb : peer_disconnected_) cb(id);
|
|
693
|
+
for (auto& cb : peer_disconnected_) cb(id, reason);
|
|
686
694
|
}
|
|
687
695
|
|
|
688
696
|
void Node::on_writable_changed(Connection& conn, bool writable) {
|
|
@@ -849,9 +857,16 @@ std::vector<Address> Node::observed_addresses() const {
|
|
|
849
857
|
|
|
850
858
|
// ── Peer handle methods (defined here for the full Node type) ────────────────
|
|
851
859
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
860
|
+
bool Peer::send(std::string_view channel, ByteView payload) const {
|
|
861
|
+
// Deliberately by id, not by this handle's route. The route existed to skip the
|
|
862
|
+
// directory lookup on the reply path — but answering "is there room?" needs the
|
|
863
|
+
// peer's writability and its in-transit counter, both of which live in the
|
|
864
|
+
// directory, so the lookup is unavoidable the moment send() has an answer to
|
|
865
|
+
// give. Once it is being paid anyway, the captured route buys nothing and can
|
|
866
|
+
// only be wrong: a handle outlives its connection (a relayed link superseded by
|
|
867
|
+
// a direct one, a dial race, a reconnect), and sending on the superseded one
|
|
868
|
+
// drops the message with nothing said. The peer is what the caller means.
|
|
869
|
+
return node_->send(id_, channel, payload);
|
|
855
870
|
}
|
|
856
871
|
|
|
857
872
|
void Peer::disconnect() const {
|
|
@@ -193,6 +193,14 @@ public:
|
|
|
193
193
|
/// low-water mark, and an application that keeps going regardless
|
|
194
194
|
/// will eventually have the peer dropped as a slow consumer. Wait for
|
|
195
195
|
/// on_peer_writable instead. Also false if the peer is not connected.
|
|
196
|
+
///
|
|
197
|
+
/// "Stop" is meant literally, and yielding is part of it: the mark is
|
|
198
|
+
/// re-tested inside the reactor task this call hands off to, and it is
|
|
199
|
+
/// that test which flips the peer to unwritable and later raises
|
|
200
|
+
/// on_peer_writable. A caller that answers a false by looping straight
|
|
201
|
+
/// back into send() therefore starves the very thread that would tell
|
|
202
|
+
/// it to stop — the queue keeps growing while the signal it is waiting
|
|
203
|
+
/// for never gets a turn to be produced.
|
|
196
204
|
bool send(const PeerId& to, std::string_view channel, ByteView payload);
|
|
197
205
|
/// Send raw bytes on a named channel to every connected peer.
|
|
198
206
|
/// @return whether *every* one of them still has room — a fan-out can only
|
|
@@ -205,12 +213,24 @@ public:
|
|
|
205
213
|
/// transfer, a large stream — address peers individually with send().
|
|
206
214
|
bool broadcast(std::string_view channel, ByteView payload);
|
|
207
215
|
|
|
208
|
-
/// Whether a peer's send queue
|
|
209
|
-
///
|
|
210
|
-
///
|
|
211
|
-
///
|
|
212
|
-
///
|
|
213
|
-
|
|
216
|
+
/// Whether a peer's send queue has room for more. False for a peer that is
|
|
217
|
+
/// not connected.
|
|
218
|
+
///
|
|
219
|
+
/// The same question send() answers, asked without sending anything: it
|
|
220
|
+
/// weighs both halves of what the peer is carrying — the bytes the reactor
|
|
221
|
+
/// has queued, and the bytes a caller has handed to send() that the reactor
|
|
222
|
+
/// has not taken up yet. So it is safe to poll: a caller that has just filled
|
|
223
|
+
/// the queue in a tight loop keeps being told "no room" until the reactor has
|
|
224
|
+
/// actually looked at what it was given, rather than being told "go on"
|
|
225
|
+
/// because nothing observable has changed yet.
|
|
226
|
+
///
|
|
227
|
+
/// It stays a hint about a queue that drains as it is read, so the ordinary
|
|
228
|
+
/// flow is unchanged: the signal to stop is the return of send(), and the
|
|
229
|
+
/// signal to resume is on_peer_writable. This is for a caller that must wait
|
|
230
|
+
/// for room on a thread of its own — the event alone cannot serve it, because
|
|
231
|
+
/// a queue that filled *only* with bytes still in transit never crossed
|
|
232
|
+
/// anything on the connection and so raises no event when they drain.
|
|
233
|
+
bool peer_writable(const PeerId& id) const override;
|
|
214
234
|
|
|
215
235
|
// — events (register before start(); invoked on a reactor thread). Multiple
|
|
216
236
|
// listeners are supported, so subsystems and the app can both subscribe. —
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
#include "librats/peer/peer_id.h"
|
|
18
18
|
#include "librats/peer/peer_info.h"
|
|
19
19
|
#include "librats/core/address.h"
|
|
20
|
+
#include "librats/core/types.h" // CloseReason
|
|
20
21
|
|
|
21
22
|
#include <cstdint>
|
|
22
23
|
#include <functional>
|
|
@@ -33,7 +34,12 @@ public:
|
|
|
33
34
|
using MessageHandler = std::function<void(const Peer&, ByteView)>;
|
|
34
35
|
|
|
35
36
|
using PeerEventHandler = std::function<void(const Peer&)>;
|
|
36
|
-
|
|
37
|
+
/// A peer went away, and why. The reason matters as much as the event: an
|
|
38
|
+
/// application dropped as a slow consumer (CloseReason::SlowConsumer) has to
|
|
39
|
+
/// slow down, while one whose peer simply left should reconnect — and without
|
|
40
|
+
/// the reason those look identical, so the usual answer to both is to redial
|
|
41
|
+
/// and repeat whatever caused it.
|
|
42
|
+
using PeerDisconnectHandler = std::function<void(const PeerId&, CloseReason)>;
|
|
37
43
|
using DialFailedHandler = std::function<void(const Address&)>;
|
|
38
44
|
|
|
39
45
|
virtual const PeerId& local_id() const = 0;
|
|
@@ -49,7 +55,9 @@ public:
|
|
|
49
55
|
/// Send to one peer. @return whether that peer's send queue still has room;
|
|
50
56
|
/// false means "stop and wait for on_peer_writable" — the message is queued
|
|
51
57
|
/// either way, but continuing past this is what gets a peer dropped as a slow
|
|
52
|
-
/// consumer. Also false if the peer is not connected.
|
|
58
|
+
/// consumer. Also false if the peer is not connected. Wait on the event and
|
|
59
|
+
/// not on a poll of the queue's state: the answer is re-derived on the reactor
|
|
60
|
+
/// thread, so a caller that spins instead of yielding never lets it change.
|
|
53
61
|
virtual bool send(const PeerId& to, MessageType type, ByteView payload) = 0;
|
|
54
62
|
/// Send to every connected peer. @return whether *every* one of them still
|
|
55
63
|
/// has room, so a subsystem that fans out can pause on the slowest.
|
|
@@ -72,6 +80,15 @@ public:
|
|
|
72
80
|
/// subsystem that never checks that return never needs this either. Runs on a
|
|
73
81
|
/// reactor thread. Default: not offered (nothing subscribes).
|
|
74
82
|
virtual void on_peer_writable(PeerEventHandler /*handler*/) {}
|
|
83
|
+
/// The same question send() answers — "has this peer room for more?" — asked
|
|
84
|
+
/// without sending anything. For a subsystem that paces a long fan-out from a
|
|
85
|
+
/// thread of its own (StorageManager streaming a snapshot): the event alone
|
|
86
|
+
/// cannot serve it, because a queue filled *only* with bytes handed to send()
|
|
87
|
+
/// that the reactor has not taken up yet never crossed anything on the
|
|
88
|
+
/// connection, so nothing raises on_peer_writable when they drain. Safe to
|
|
89
|
+
/// poll — it weighs those in-flight bytes too. False for an unknown peer.
|
|
90
|
+
/// Default: always writable, all a mock that merely moves messages can claim.
|
|
91
|
+
virtual bool peer_writable(const PeerId& /*id*/) const { return true; }
|
|
75
92
|
};
|
|
76
93
|
|
|
77
94
|
struct NodeContext; // node/node_context.h — bundles network + events + services
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
* @file peer.h
|
|
5
5
|
* @brief A lightweight handle to a connected peer.
|
|
6
6
|
*
|
|
7
|
-
* Peer is a value passed to callbacks. It carries the peer's id and its
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* consult the directory
|
|
7
|
+
* Peer is a value passed to callbacks. It carries the peer's id and its route
|
|
8
|
+
* (which reactor + connection). disconnect() uses the route, reaching that exact
|
|
9
|
+
* connection with no directory lookup. send() goes by id instead: it has to
|
|
10
|
+
* consult the directory anyway for the backpressure answer it returns, and by
|
|
11
|
+
* then the peer's *current* route is the better destination — a handle can
|
|
12
|
+
* outlive the link it names. info() consults the directory on demand too.
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
#include "librats/util/rats_export.h"
|
|
@@ -27,8 +29,17 @@ class RATS_API Peer {
|
|
|
27
29
|
public:
|
|
28
30
|
const PeerId& id() const noexcept { return id_; }
|
|
29
31
|
|
|
30
|
-
/// Send bytes on a named application channel
|
|
31
|
-
|
|
32
|
+
/// Send bytes on a named application channel, to this peer over whichever
|
|
33
|
+
/// connection currently serves it.
|
|
34
|
+
///
|
|
35
|
+
/// @return whether that peer's queue still has room — the same answer, and the
|
|
36
|
+
/// same contract, as Node::send(): false means stop and wait for
|
|
37
|
+
/// on_peer_writable rather than keep going. A handler that replies
|
|
38
|
+
/// through this handle is the most ordinary way to write to a peer, so
|
|
39
|
+
/// it has to be able to feel backpressure like any other sender; while
|
|
40
|
+
/// this returned void it could not, and the bytes it queued were
|
|
41
|
+
/// invisible to peer_writable() into the bargain.
|
|
42
|
+
bool send(std::string_view channel, ByteView payload) const;
|
|
32
43
|
|
|
33
44
|
/// Request this peer be disconnected.
|
|
34
45
|
void disconnect() const;
|
|
@@ -32,6 +32,7 @@ class PlaintextSession final : public Session {
|
|
|
32
32
|
public:
|
|
33
33
|
explicit PlaintextSession(PeerId remote) : remote_id_(remote) {}
|
|
34
34
|
bool encrypt(ByteView plain, Bytes& out) override { out.assign(plain.begin(), plain.end()); return true; }
|
|
35
|
+
size_t overhead() const noexcept override { return 0; }
|
|
35
36
|
bool decrypt(ByteView cipher, Bytes& out) override { out.assign(cipher.begin(), cipher.end()); return true; }
|
|
36
37
|
const PeerId& remote_id() const override { return remote_id_; }
|
|
37
38
|
bool is_secure() const override { return false; }
|
|
@@ -30,6 +30,13 @@ public:
|
|
|
30
30
|
/// The remote peer's identity, proven during the handshake.
|
|
31
31
|
virtual const PeerId& remote_id() const = 0;
|
|
32
32
|
|
|
33
|
+
/// Bytes encrypt() adds to a plaintext of any size (an AEAD tag, typically).
|
|
34
|
+
/// The send path needs the ciphertext's size *before* encrypting it, because
|
|
35
|
+
/// a message it turns out it cannot frame must be refused without a nonce
|
|
36
|
+
/// having been spent on it — the counters run in lockstep at both ends, so a
|
|
37
|
+
/// message encrypted and then not sent would break every one after it.
|
|
38
|
+
virtual size_t overhead() const noexcept = 0;
|
|
39
|
+
|
|
33
40
|
/// True if traffic is actually encrypted (false for the plaintext passthrough).
|
|
34
41
|
virtual bool is_secure() const = 0;
|
|
35
42
|
};
|