librats 0.7.2 → 0.9.0
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 -4
- package/lib/index.d.ts +9 -22
- package/native-src/CMakeLists.txt +132 -50
- package/native-src/cmake/ratsConfig.cmake.in +8 -0
- package/native-src/src/bt_network.cpp +296 -183
- package/native-src/src/bt_network.h +25 -5
- package/native-src/src/crypto/sha256.c +1 -1
- package/native-src/src/dht.cpp +297 -47
- package/native-src/src/dht.h +70 -6
- package/native-src/src/file_transfer.cpp +1185 -1578
- package/native-src/src/file_transfer.h +240 -521
- package/native-src/src/io_poller.cpp +917 -0
- package/native-src/src/io_poller.h +138 -0
- package/native-src/src/krpc.cpp +161 -96
- package/native-src/src/krpc.h +18 -4
- package/native-src/src/librats.cpp +812 -1236
- package/native-src/src/librats.h +207 -208
- package/native-src/src/librats_bittorrent.cpp +1 -5
- package/native-src/src/librats_c.cpp +22 -39
- package/native-src/src/librats_discovery.cpp +377 -0
- package/native-src/src/librats_encryption.cpp +130 -283
- package/native-src/src/librats_file_transfer.cpp +27 -109
- package/native-src/src/librats_gossipsub.cpp +1 -5
- package/native-src/src/librats_ice.cpp +5 -1
- package/native-src/src/librats_log_macros.h +36 -0
- package/native-src/src/librats_logging.cpp +1 -7
- package/native-src/src/librats_mdns.cpp +1 -11
- package/native-src/src/librats_persistence.cpp +1 -11
- package/native-src/src/librats_reconnection.cpp +2 -13
- package/native-src/src/librats_statistic.cpp +105 -0
- package/native-src/src/socket.cpp +15 -3
- package/package.json +1 -1
- package/scripts/build-librats.js +3 -0
- package/scripts/prepare-package.js +10 -0
- package/src/librats_node.cpp +53 -64
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file io_poller.h
|
|
5
|
+
* @brief Platform-optimal I/O multiplexing abstraction
|
|
6
|
+
*
|
|
7
|
+
* Provides a unified interface over platform-specific I/O multiplexers:
|
|
8
|
+
* - Linux: epoll (O(1) per event, scales to millions of fds)
|
|
9
|
+
* - macOS/BSD: kqueue (O(1) per event, scales to millions of fds)
|
|
10
|
+
* - Windows: IOCP (true async completion ports, O(1) per event)
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* auto poller = IOPoller::create();
|
|
14
|
+
* poller->add(fd, PollIn);
|
|
15
|
+
*
|
|
16
|
+
* PollResult results[64];
|
|
17
|
+
* int n = poller->wait(results, 64, 100); // 100ms timeout
|
|
18
|
+
* for (int i = 0; i < n; i++) {
|
|
19
|
+
* if (results[i].events & PollIn) handle_read(results[i].fd);
|
|
20
|
+
* if (results[i].events & PollOut) handle_write(results[i].fd);
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
#include "socket.h"
|
|
25
|
+
|
|
26
|
+
#include <memory>
|
|
27
|
+
#include <cstdint>
|
|
28
|
+
|
|
29
|
+
namespace librats {
|
|
30
|
+
|
|
31
|
+
//=============================================================================
|
|
32
|
+
// Poll Event Flags
|
|
33
|
+
//=============================================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @brief I/O event flags for polling
|
|
37
|
+
*/
|
|
38
|
+
enum PollFlags : uint32_t {
|
|
39
|
+
PollNone = 0,
|
|
40
|
+
PollIn = 1 << 0, ///< Socket is readable (data available or connection accepted)
|
|
41
|
+
PollOut = 1 << 1, ///< Socket is writable (can send or connect completed)
|
|
42
|
+
PollErr = 1 << 2, ///< Error condition on socket
|
|
43
|
+
PollHup = 1 << 3, ///< Hang up / peer disconnected
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
inline uint32_t operator|(PollFlags a, PollFlags b) {
|
|
47
|
+
return static_cast<uint32_t>(a) | static_cast<uint32_t>(b);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//=============================================================================
|
|
51
|
+
// Poll Result
|
|
52
|
+
//=============================================================================
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @brief Result entry from a poll wait
|
|
56
|
+
*/
|
|
57
|
+
struct PollResult {
|
|
58
|
+
socket_t fd; ///< The socket that has events
|
|
59
|
+
uint32_t events; ///< Bitmask of PollFlags that occurred
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
//=============================================================================
|
|
63
|
+
// IOPoller Abstract Interface
|
|
64
|
+
//=============================================================================
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @brief Abstract I/O multiplexer
|
|
68
|
+
*
|
|
69
|
+
* Thread-safety:
|
|
70
|
+
* - add/modify/remove: Safe to call from any thread (epoll_ctl is thread-safe,
|
|
71
|
+
* kqueue changes are atomic, WSAPoll rebuilds on wait).
|
|
72
|
+
* - wait: Should be called from a single I/O thread.
|
|
73
|
+
* - add/modify/remove can be called concurrently with wait().
|
|
74
|
+
*/
|
|
75
|
+
class IOPoller {
|
|
76
|
+
public:
|
|
77
|
+
virtual ~IOPoller() = default;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @brief Create the platform-optimal poller instance
|
|
81
|
+
*
|
|
82
|
+
* Returns:
|
|
83
|
+
* - EpollPoller on Linux
|
|
84
|
+
* - KqueuePoller on macOS/FreeBSD
|
|
85
|
+
* - IocpPoller on Windows (I/O Completion Ports)
|
|
86
|
+
*/
|
|
87
|
+
static std::unique_ptr<IOPoller> create();
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @brief Add a socket to the poll set
|
|
91
|
+
*
|
|
92
|
+
* @param fd Socket to monitor
|
|
93
|
+
* @param events Bitmask of PollFlags to watch for
|
|
94
|
+
* @return true on success
|
|
95
|
+
*/
|
|
96
|
+
virtual bool add(socket_t fd, uint32_t events) = 0;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @brief Modify the event mask for a monitored socket
|
|
100
|
+
*
|
|
101
|
+
* @param fd Socket already in the poll set
|
|
102
|
+
* @param events New bitmask of PollFlags
|
|
103
|
+
* @return true on success
|
|
104
|
+
*/
|
|
105
|
+
virtual bool modify(socket_t fd, uint32_t events) = 0;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @brief Remove a socket from the poll set
|
|
109
|
+
*
|
|
110
|
+
* @param fd Socket to remove
|
|
111
|
+
* @return true on success (false if fd was not registered)
|
|
112
|
+
*/
|
|
113
|
+
virtual bool remove(socket_t fd) = 0;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @brief Wait for I/O events
|
|
117
|
+
*
|
|
118
|
+
* Blocks until events occur or timeout expires.
|
|
119
|
+
*
|
|
120
|
+
* @param results Array to fill with ready socket events
|
|
121
|
+
* @param max_results Maximum entries in results array
|
|
122
|
+
* @param timeout_ms Timeout in milliseconds (-1 = block forever, 0 = non-blocking)
|
|
123
|
+
* @return Number of ready descriptors (0 on timeout, -1 on error)
|
|
124
|
+
*/
|
|
125
|
+
virtual int wait(PollResult* results, int max_results, int timeout_ms) = 0;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @brief Get the backend name (for logging/diagnostics)
|
|
129
|
+
*/
|
|
130
|
+
virtual const char* name() const = 0;
|
|
131
|
+
|
|
132
|
+
// Non-copyable
|
|
133
|
+
IOPoller() = default;
|
|
134
|
+
IOPoller(const IOPoller&) = delete;
|
|
135
|
+
IOPoller& operator=(const IOPoller&) = delete;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
} // namespace librats
|
package/native-src/src/krpc.cpp
CHANGED
|
@@ -183,6 +183,15 @@ BencodeValue KrpcProtocol::encode_query(const KrpcMessage& message) {
|
|
|
183
183
|
break;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
// BEP 32: advertise which node families we want back
|
|
187
|
+
if (!message.want.empty()) {
|
|
188
|
+
BencodeValue want_list = BencodeValue::create_list();
|
|
189
|
+
for (const auto& w : message.want) {
|
|
190
|
+
want_list.push_back(BencodeValue(w));
|
|
191
|
+
}
|
|
192
|
+
args["want"] = want_list;
|
|
193
|
+
}
|
|
194
|
+
|
|
186
195
|
root["a"] = args;
|
|
187
196
|
return root;
|
|
188
197
|
}
|
|
@@ -198,13 +207,24 @@ BencodeValue KrpcProtocol::encode_response(const KrpcMessage& message) {
|
|
|
198
207
|
BencodeValue response = BencodeValue::create_dict();
|
|
199
208
|
response["id"] = BencodeValue(node_id_to_string(message.response_id));
|
|
200
209
|
|
|
201
|
-
// Add nodes if present
|
|
210
|
+
// Add nodes if present. BEP 32: IPv4 nodes go in "nodes" (26 bytes each),
|
|
211
|
+
// IPv6 nodes go in "nodes6" (38 bytes each).
|
|
202
212
|
if (!message.nodes.empty()) {
|
|
203
|
-
std::string
|
|
213
|
+
std::string compact_nodes_v4;
|
|
214
|
+
std::string compact_nodes_v6;
|
|
204
215
|
for (const auto& node : message.nodes) {
|
|
205
|
-
|
|
216
|
+
if (network_utils::is_valid_ipv6(node.ip)) {
|
|
217
|
+
compact_nodes_v6 += compact_node_info(node);
|
|
218
|
+
} else {
|
|
219
|
+
compact_nodes_v4 += compact_node_info(node);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (!compact_nodes_v4.empty()) {
|
|
223
|
+
response["nodes"] = BencodeValue(compact_nodes_v4);
|
|
224
|
+
}
|
|
225
|
+
if (!compact_nodes_v6.empty()) {
|
|
226
|
+
response["nodes6"] = BencodeValue(compact_nodes_v6);
|
|
206
227
|
}
|
|
207
|
-
response["nodes"] = BencodeValue(compact_nodes);
|
|
208
228
|
}
|
|
209
229
|
|
|
210
230
|
// Add peers if present
|
|
@@ -220,8 +240,18 @@ BencodeValue KrpcProtocol::encode_response(const KrpcMessage& message) {
|
|
|
220
240
|
if (!message.token.empty()) {
|
|
221
241
|
response["token"] = BencodeValue(message.token);
|
|
222
242
|
}
|
|
223
|
-
|
|
243
|
+
|
|
224
244
|
root["r"] = response;
|
|
245
|
+
|
|
246
|
+
// BEP 42: echo the requester's external address as a top-level "ip" field
|
|
247
|
+
// (compact 6-byte IPv4 / 18-byte IPv6 ip+port).
|
|
248
|
+
if (!message.external_ip.empty()) {
|
|
249
|
+
std::string compact = compact_peer_info(Peer(message.external_ip, message.external_port));
|
|
250
|
+
if (!compact.empty()) {
|
|
251
|
+
root["ip"] = BencodeValue(compact);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
225
255
|
return root;
|
|
226
256
|
}
|
|
227
257
|
|
|
@@ -295,7 +325,17 @@ std::unique_ptr<KrpcMessage> KrpcProtocol::decode_query(const BencodeValue& data
|
|
|
295
325
|
}
|
|
296
326
|
|
|
297
327
|
message->sender_id = string_to_node_id(args["id"].as_string());
|
|
298
|
-
|
|
328
|
+
|
|
329
|
+
// BEP 32: optional "want" list specifying desired node families
|
|
330
|
+
if (args.has_key("want")) {
|
|
331
|
+
const BencodeValue& want_list = args["want"];
|
|
332
|
+
if (want_list.is_list()) {
|
|
333
|
+
for (size_t i = 0; i < want_list.size(); ++i) {
|
|
334
|
+
message->want.push_back(want_list[i].as_string());
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
299
339
|
switch (message->query_type) {
|
|
300
340
|
case KrpcQueryType::Ping:
|
|
301
341
|
// No additional arguments
|
|
@@ -347,10 +387,15 @@ std::unique_ptr<KrpcMessage> KrpcProtocol::decode_response(const BencodeValue& d
|
|
|
347
387
|
|
|
348
388
|
message->response_id = string_to_node_id(response["id"].as_string());
|
|
349
389
|
|
|
350
|
-
// Parse nodes if present
|
|
390
|
+
// Parse nodes if present (BEP 32: "nodes" = IPv4, "nodes6" = IPv6)
|
|
351
391
|
if (response.has_key("nodes")) {
|
|
352
392
|
std::string compact_nodes = response["nodes"].as_string();
|
|
353
|
-
message->nodes = parse_compact_node_info(compact_nodes);
|
|
393
|
+
message->nodes = parse_compact_node_info(compact_nodes, /*ipv6=*/false);
|
|
394
|
+
}
|
|
395
|
+
if (response.has_key("nodes6")) {
|
|
396
|
+
std::string compact_nodes6 = response["nodes6"].as_string();
|
|
397
|
+
auto nodes6 = parse_compact_node_info(compact_nodes6, /*ipv6=*/true);
|
|
398
|
+
message->nodes.insert(message->nodes.end(), nodes6.begin(), nodes6.end());
|
|
354
399
|
}
|
|
355
400
|
|
|
356
401
|
// Parse peers if present
|
|
@@ -369,7 +414,16 @@ std::unique_ptr<KrpcMessage> KrpcProtocol::decode_response(const BencodeValue& d
|
|
|
369
414
|
if (response.has_key("token")) {
|
|
370
415
|
message->token = response["token"].as_string();
|
|
371
416
|
}
|
|
372
|
-
|
|
417
|
+
|
|
418
|
+
// BEP 42: top-level "ip" field tells us our external address as the responder sees it.
|
|
419
|
+
if (data.has_key("ip")) {
|
|
420
|
+
auto endpoints = parse_compact_peer_info(data["ip"].as_string());
|
|
421
|
+
if (!endpoints.empty()) {
|
|
422
|
+
message->external_ip = endpoints[0].ip;
|
|
423
|
+
message->external_port = endpoints[0].port;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
373
427
|
return message;
|
|
374
428
|
}
|
|
375
429
|
|
|
@@ -415,132 +469,143 @@ NodeId KrpcProtocol::string_to_node_id(const std::string& str) {
|
|
|
415
469
|
return id;
|
|
416
470
|
}
|
|
417
471
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
uint32_t ip = ntohl(addr.s_addr);
|
|
427
|
-
result += static_cast<char>((ip >> 24) & 0xFF);
|
|
428
|
-
result += static_cast<char>((ip >> 16) & 0xFF);
|
|
429
|
-
result += static_cast<char>((ip >> 8) & 0xFF);
|
|
430
|
-
result += static_cast<char>(ip & 0xFF);
|
|
431
|
-
} else {
|
|
432
|
-
// Invalid IP, use 0.0.0.0
|
|
433
|
-
result += "\x00\x00\x00\x00";
|
|
472
|
+
// Append a compact IP address (4 bytes for IPv4, 16 bytes for IPv6) to `out`.
|
|
473
|
+
// Returns the number of address bytes written, or 0 on failure.
|
|
474
|
+
static size_t append_compact_address(const std::string& ip, std::string& out) {
|
|
475
|
+
if (network_utils::is_valid_ipv6(ip)) {
|
|
476
|
+
struct in6_addr addr6;
|
|
477
|
+
if (inet_pton(AF_INET6, ip.c_str(), &addr6) == 1) {
|
|
478
|
+
out.append(reinterpret_cast<const char*>(addr6.s6_addr), 16);
|
|
479
|
+
return 16;
|
|
434
480
|
}
|
|
435
|
-
|
|
436
|
-
// Not IPv4, use 0.0.0.0
|
|
437
|
-
result += "\x00\x00\x00\x00";
|
|
481
|
+
return 0;
|
|
438
482
|
}
|
|
439
|
-
|
|
483
|
+
|
|
484
|
+
struct in_addr addr;
|
|
485
|
+
if (inet_pton(AF_INET, ip.c_str(), &addr) == 1) {
|
|
486
|
+
uint32_t v = ntohl(addr.s_addr);
|
|
487
|
+
out += static_cast<char>((v >> 24) & 0xFF);
|
|
488
|
+
out += static_cast<char>((v >> 16) & 0xFF);
|
|
489
|
+
out += static_cast<char>((v >> 8) & 0xFF);
|
|
490
|
+
out += static_cast<char>(v & 0xFF);
|
|
491
|
+
return 4;
|
|
492
|
+
}
|
|
493
|
+
// Invalid IPv4, use 0.0.0.0
|
|
494
|
+
out.append(4, '\x00');
|
|
495
|
+
return 4;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Read a compact IP address of `addr_len` bytes (4=IPv4, 16=IPv6) at compact_info[offset]
|
|
499
|
+
// into a printable string.
|
|
500
|
+
static std::string read_compact_address(const std::string& compact_info, size_t offset, size_t addr_len) {
|
|
501
|
+
if (addr_len == 16) {
|
|
502
|
+
struct in6_addr addr6;
|
|
503
|
+
memcpy(addr6.s6_addr, compact_info.data() + offset, 16);
|
|
504
|
+
char ip_str[INET6_ADDRSTRLEN];
|
|
505
|
+
inet_ntop(AF_INET6, &addr6, ip_str, INET6_ADDRSTRLEN);
|
|
506
|
+
return std::string(ip_str);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
struct in_addr addr;
|
|
510
|
+
uint32_t ip = 0;
|
|
511
|
+
ip |= (static_cast<uint8_t>(compact_info[offset]) << 24);
|
|
512
|
+
ip |= (static_cast<uint8_t>(compact_info[offset + 1]) << 16);
|
|
513
|
+
ip |= (static_cast<uint8_t>(compact_info[offset + 2]) << 8);
|
|
514
|
+
ip |= static_cast<uint8_t>(compact_info[offset + 3]);
|
|
515
|
+
addr.s_addr = htonl(ip);
|
|
516
|
+
char ip_str[INET_ADDRSTRLEN];
|
|
517
|
+
inet_ntop(AF_INET, &addr, ip_str, INET_ADDRSTRLEN);
|
|
518
|
+
return std::string(ip_str);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
std::string KrpcProtocol::compact_peer_info(const Peer& peer) {
|
|
522
|
+
std::string result;
|
|
523
|
+
result.reserve(18);
|
|
524
|
+
|
|
525
|
+
// IP address (4 bytes IPv4 / 16 bytes IPv6)
|
|
526
|
+
append_compact_address(peer.ip, result);
|
|
527
|
+
|
|
440
528
|
// Convert port to 2 bytes (network byte order)
|
|
441
529
|
result += static_cast<char>((peer.port >> 8) & 0xFF);
|
|
442
530
|
result += static_cast<char>(peer.port & 0xFF);
|
|
443
|
-
|
|
531
|
+
|
|
444
532
|
return result;
|
|
445
533
|
}
|
|
446
534
|
|
|
447
535
|
std::string KrpcProtocol::compact_node_info(const KrpcNode& node) {
|
|
448
536
|
std::string result;
|
|
449
|
-
result.reserve(
|
|
450
|
-
|
|
537
|
+
result.reserve(38);
|
|
538
|
+
|
|
451
539
|
// Node ID (20 bytes)
|
|
452
540
|
result += node_id_to_string(node.id);
|
|
453
|
-
|
|
454
|
-
// IP address (4 bytes)
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
if (inet_pton(AF_INET, node.ip.c_str(), &addr) == 1) {
|
|
458
|
-
uint32_t ip = ntohl(addr.s_addr);
|
|
459
|
-
result += static_cast<char>((ip >> 24) & 0xFF);
|
|
460
|
-
result += static_cast<char>((ip >> 16) & 0xFF);
|
|
461
|
-
result += static_cast<char>((ip >> 8) & 0xFF);
|
|
462
|
-
result += static_cast<char>(ip & 0xFF);
|
|
463
|
-
} else {
|
|
464
|
-
// Invalid IP, use 0.0.0.0
|
|
465
|
-
result += "\x00\x00\x00\x00";
|
|
466
|
-
}
|
|
467
|
-
} else {
|
|
468
|
-
// Not IPv4, use 0.0.0.0
|
|
469
|
-
result += "\x00\x00\x00\x00";
|
|
470
|
-
}
|
|
471
|
-
|
|
541
|
+
|
|
542
|
+
// IP address (4 bytes IPv4 / 16 bytes IPv6)
|
|
543
|
+
append_compact_address(node.ip, result);
|
|
544
|
+
|
|
472
545
|
// Port (2 bytes, network byte order)
|
|
473
546
|
result += static_cast<char>((node.port >> 8) & 0xFF);
|
|
474
547
|
result += static_cast<char>(node.port & 0xFF);
|
|
475
|
-
|
|
548
|
+
|
|
476
549
|
return result;
|
|
477
550
|
}
|
|
478
551
|
|
|
479
552
|
std::vector<Peer> KrpcProtocol::parse_compact_peer_info(const std::string& compact_info) {
|
|
480
553
|
std::vector<Peer> peers;
|
|
481
|
-
|
|
482
|
-
|
|
554
|
+
|
|
555
|
+
// BEP 5/BEP 7: each "values" entry is a single compact peer: 6 bytes (IPv4) or 18 bytes (IPv6).
|
|
556
|
+
// Detect the family from the entry length. An exact length of 18 is treated as one IPv6 peer;
|
|
557
|
+
// otherwise we chunk by 6 (some implementations concatenate multiple IPv4 peers).
|
|
558
|
+
size_t record_size = 6;
|
|
559
|
+
size_t addr_len = 4;
|
|
560
|
+
if (compact_info.size() == 18) {
|
|
561
|
+
record_size = 18;
|
|
562
|
+
addr_len = 16;
|
|
563
|
+
} else if (compact_info.size() % 6 != 0) {
|
|
483
564
|
LOG_KRPC_WARN("Invalid compact peer info size: " << compact_info.size());
|
|
484
565
|
return peers;
|
|
485
566
|
}
|
|
486
|
-
|
|
487
|
-
for (size_t i = 0; i
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
ip |= (static_cast<uint8_t>(compact_info[i]) << 24);
|
|
491
|
-
ip |= (static_cast<uint8_t>(compact_info[i + 1]) << 16);
|
|
492
|
-
ip |= (static_cast<uint8_t>(compact_info[i + 2]) << 8);
|
|
493
|
-
ip |= static_cast<uint8_t>(compact_info[i + 3]);
|
|
494
|
-
|
|
495
|
-
struct in_addr addr;
|
|
496
|
-
addr.s_addr = htonl(ip);
|
|
497
|
-
char ip_str[INET_ADDRSTRLEN];
|
|
498
|
-
inet_ntop(AF_INET, &addr, ip_str, INET_ADDRSTRLEN);
|
|
499
|
-
|
|
500
|
-
// Extract port (2 bytes)
|
|
567
|
+
|
|
568
|
+
for (size_t i = 0; i + record_size <= compact_info.size(); i += record_size) {
|
|
569
|
+
std::string ip_str = read_compact_address(compact_info, i, addr_len);
|
|
570
|
+
|
|
501
571
|
uint16_t port = 0;
|
|
502
|
-
port |= (static_cast<uint8_t>(compact_info[i +
|
|
503
|
-
port |= static_cast<uint8_t>(compact_info[i +
|
|
504
|
-
|
|
572
|
+
port |= (static_cast<uint8_t>(compact_info[i + addr_len]) << 8);
|
|
573
|
+
port |= static_cast<uint8_t>(compact_info[i + addr_len + 1]);
|
|
574
|
+
|
|
505
575
|
peers.emplace_back(ip_str, port);
|
|
506
576
|
}
|
|
507
|
-
|
|
577
|
+
|
|
508
578
|
return peers;
|
|
509
579
|
}
|
|
510
580
|
|
|
511
|
-
std::vector<KrpcNode> KrpcProtocol::parse_compact_node_info(const std::string& compact_info) {
|
|
581
|
+
std::vector<KrpcNode> KrpcProtocol::parse_compact_node_info(const std::string& compact_info, bool ipv6) {
|
|
512
582
|
std::vector<KrpcNode> nodes;
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
583
|
+
|
|
584
|
+
const size_t addr_len = ipv6 ? 16 : 4;
|
|
585
|
+
const size_t record_size = 20 + addr_len + 2; // 26 (IPv4) or 38 (IPv6)
|
|
586
|
+
|
|
587
|
+
if (compact_info.size() % record_size != 0) {
|
|
588
|
+
LOG_KRPC_WARN("Invalid compact node info size: " << compact_info.size()
|
|
589
|
+
<< " (expected multiple of " << record_size << ")");
|
|
516
590
|
return nodes;
|
|
517
591
|
}
|
|
518
|
-
|
|
519
|
-
for (size_t i = 0; i < compact_info.size(); i +=
|
|
592
|
+
|
|
593
|
+
for (size_t i = 0; i < compact_info.size(); i += record_size) {
|
|
520
594
|
// Extract node ID (20 bytes)
|
|
521
595
|
NodeId node_id;
|
|
522
596
|
std::copy_n(compact_info.begin() + i, 20, node_id.begin());
|
|
523
|
-
|
|
524
|
-
// Extract IP address (4 bytes)
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
ip |= (static_cast<uint8_t>(compact_info[i + 21]) << 16);
|
|
528
|
-
ip |= (static_cast<uint8_t>(compact_info[i + 22]) << 8);
|
|
529
|
-
ip |= static_cast<uint8_t>(compact_info[i + 23]);
|
|
530
|
-
|
|
531
|
-
struct in_addr addr;
|
|
532
|
-
addr.s_addr = htonl(ip);
|
|
533
|
-
char ip_str[INET_ADDRSTRLEN];
|
|
534
|
-
inet_ntop(AF_INET, &addr, ip_str, INET_ADDRSTRLEN);
|
|
535
|
-
|
|
597
|
+
|
|
598
|
+
// Extract IP address (4 or 16 bytes)
|
|
599
|
+
std::string ip_str = read_compact_address(compact_info, i + 20, addr_len);
|
|
600
|
+
|
|
536
601
|
// Extract port (2 bytes)
|
|
537
602
|
uint16_t port = 0;
|
|
538
|
-
port |= (static_cast<uint8_t>(compact_info[i +
|
|
539
|
-
port |= static_cast<uint8_t>(compact_info[i +
|
|
540
|
-
|
|
603
|
+
port |= (static_cast<uint8_t>(compact_info[i + 20 + addr_len]) << 8);
|
|
604
|
+
port |= static_cast<uint8_t>(compact_info[i + 20 + addr_len + 1]);
|
|
605
|
+
|
|
541
606
|
nodes.emplace_back(node_id, ip_str, port);
|
|
542
607
|
}
|
|
543
|
-
|
|
608
|
+
|
|
544
609
|
return nodes;
|
|
545
610
|
}
|
|
546
611
|
|
package/native-src/src/krpc.h
CHANGED
|
@@ -71,15 +71,26 @@ struct KrpcMessage {
|
|
|
71
71
|
bool implied_port; // BEP 5: if true, use UDP source port instead of 'port' field
|
|
72
72
|
std::string token;
|
|
73
73
|
|
|
74
|
+
// BEP 32: requested node families for queries ("n4" and/or "n6").
|
|
75
|
+
// Empty means "no want specified" (responder defaults to the family the request arrived on).
|
|
76
|
+
std::vector<std::string> want;
|
|
77
|
+
|
|
74
78
|
// For responses
|
|
75
79
|
NodeId response_id;
|
|
76
|
-
std::vector<KrpcNode> nodes;
|
|
80
|
+
std::vector<KrpcNode> nodes; // may mix IPv4/IPv6 nodes; encoder splits into nodes/nodes6
|
|
77
81
|
std::vector<Peer> peers;
|
|
78
|
-
|
|
82
|
+
|
|
83
|
+
// BEP 42: top-level "ip" field (compact ip+port).
|
|
84
|
+
// On responses we SEND: the requester's external address, so they can learn how the
|
|
85
|
+
// network sees them and derive a compliant node ID.
|
|
86
|
+
// On responses we RECEIVE: our own external address as observed by the responder.
|
|
87
|
+
std::string external_ip;
|
|
88
|
+
uint16_t external_port = 0;
|
|
89
|
+
|
|
79
90
|
// For errors
|
|
80
91
|
KrpcErrorCode error_code;
|
|
81
92
|
std::string error_message;
|
|
82
|
-
|
|
93
|
+
|
|
83
94
|
KrpcMessage() : type(KrpcMessageType::Query), query_type(KrpcQueryType::Ping), sender_id(), target_id(), info_hash(), port(0), implied_port(false), response_id(), error_code(KrpcErrorCode::GenericError) {}
|
|
84
95
|
};
|
|
85
96
|
|
|
@@ -123,10 +134,13 @@ public:
|
|
|
123
134
|
*/
|
|
124
135
|
static std::string node_id_to_string(const NodeId& id);
|
|
125
136
|
static NodeId string_to_node_id(const std::string& str);
|
|
137
|
+
// Compact encodings auto-select IPv4 (6/26 bytes) or IPv6 (18/38 bytes) based on the address.
|
|
126
138
|
static std::string compact_peer_info(const Peer& peer);
|
|
127
139
|
static std::string compact_node_info(const KrpcNode& node);
|
|
140
|
+
// parse_compact_peer_info detects the family from the string length (one peer per string).
|
|
128
141
|
static std::vector<Peer> parse_compact_peer_info(const std::string& compact_info);
|
|
129
|
-
|
|
142
|
+
// parse_compact_node_info reads fixed-size records; pass ipv6=true for 38-byte ("nodes6") records.
|
|
143
|
+
static std::vector<KrpcNode> parse_compact_node_info(const std::string& compact_info, bool ipv6 = false);
|
|
130
144
|
|
|
131
145
|
private:
|
|
132
146
|
static BencodeValue encode_query(const KrpcMessage& message);
|