librats 0.5.1 → 0.5.3

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.
@@ -127,6 +127,16 @@ struct DhtNode {
127
127
  */
128
128
  using PeerDiscoveryCallback = std::function<void(const std::vector<Peer>& peers, const InfoHash& info_hash)>;
129
129
 
130
+ #ifdef RATS_SEARCH_FEATURES
131
+ /**
132
+ * Spider mode callback
133
+ * Called when a peer announces they have a torrent (announce_peer request received)
134
+ * @param info_hash The info hash being announced
135
+ * @param peer The peer that is announcing (with the port they specified)
136
+ */
137
+ using SpiderAnnounceCallback = std::function<void(const InfoHash& info_hash, const Peer& peer)>;
138
+ #endif // RATS_SEARCH_FEATURES
139
+
130
140
  /**
131
141
  * Deferred callbacks structure for avoiding deadlock
132
142
  * Callbacks are collected while holding the mutex, then invoked after releasing it
@@ -198,9 +208,10 @@ public:
198
208
  * Announce that this node is a peer for a specific info hash
199
209
  * @param info_hash The info hash to announce
200
210
  * @param port The port to announce (0 for DHT port)
211
+ * @param callback Optional callback to receive discovered peers during traversal
201
212
  * @return true if announcement started successfully, false otherwise
202
213
  */
203
- bool announce_peer(const InfoHash& info_hash, uint16_t port = 0);
214
+ bool announce_peer(const InfoHash& info_hash, uint16_t port = 0, PeerDiscoveryCallback callback = nullptr);
204
215
 
205
216
  /**
206
217
  * Get our node ID
@@ -227,18 +238,79 @@ public:
227
238
  */
228
239
  bool is_search_active(const InfoHash& info_hash) const;
229
240
 
241
+ /**
242
+ * Check if an announce is currently active for an info hash
243
+ * @param info_hash The info hash to check
244
+ * @return true if announce is active, false otherwise
245
+ */
246
+ bool is_announce_active(const InfoHash& info_hash) const;
247
+
230
248
  /**
231
249
  * Get number of active searches
232
250
  * @return Number of active searches
233
251
  */
234
252
  size_t get_active_searches_count() const;
235
253
 
254
+ /**
255
+ * Get number of active announces
256
+ * @return Number of active announces
257
+ */
258
+ size_t get_active_announces_count() const;
259
+
236
260
  /**
237
261
  * Check if DHT is running
238
262
  * @return true if running, false otherwise
239
263
  */
240
264
  bool is_running() const { return running_; }
241
265
 
266
+ #ifdef RATS_SEARCH_FEATURES
267
+ // ============================================================================
268
+ // SPIDER MODE - Aggressive node discovery and announce collection
269
+ // ============================================================================
270
+
271
+ /**
272
+ * Enable spider mode
273
+ * In spider mode:
274
+ * - Nodes are added to routing table without ping verification
275
+ * - All announce_peer requests from other peers are collected via callback
276
+ * @param enable true to enable spider mode, false to disable
277
+ */
278
+ void set_spider_mode(bool enable);
279
+
280
+ /**
281
+ * Check if spider mode is enabled
282
+ * @return true if spider mode is enabled
283
+ */
284
+ bool is_spider_mode() const { return spider_mode_; }
285
+
286
+ /**
287
+ * Set callback for announce_peer requests (spider mode)
288
+ * Called when other peers announce they have a torrent
289
+ * @param callback The callback to invoke
290
+ */
291
+ void set_spider_announce_callback(SpiderAnnounceCallback callback);
292
+
293
+ /**
294
+ * Set spider ignore mode - when true, incoming requests are not processed
295
+ * Similar to rate limiting in the original spider implementation
296
+ * @param ignore true to ignore incoming requests, false to process them
297
+ */
298
+ void set_spider_ignore(bool ignore);
299
+
300
+ /**
301
+ * Check if spider ignore mode is enabled
302
+ * @return true if ignoring incoming requests
303
+ */
304
+ bool is_spider_ignoring() const { return spider_ignore_; }
305
+
306
+ /**
307
+ * Trigger a single spider walk iteration
308
+ * Sends find_node to a random node from the routing table
309
+ * Should be called from external loop at desired frequency
310
+ */
311
+ void spider_walk();
312
+ #endif // RATS_SEARCH_FEATURES
313
+
242
314
  /**
243
315
  * Get default BitTorrent DHT bootstrap nodes
244
316
  * @return Vector of bootstrap nodes
@@ -279,10 +351,9 @@ private:
279
351
  // 1. pending_pings_mutex_ (Ping verification state, nodes_being_replaced_)
280
352
  // 2. pending_searches_mutex_ (Search state and transaction mappings)
281
353
  // 3. routing_table_mutex_ (core routing data)
282
- // 4. pending_announces_mutex_ (Announce state)
283
- // 5. announced_peers_mutex_ (Stored peer data)
284
- // 6. peer_tokens_mutex_ (Token validation data)
285
- // 7. shutdown_mutex_ (Lowest priority - can be locked independently)
354
+ // 4. announced_peers_mutex_ (Stored peer data)
355
+ // 5. peer_tokens_mutex_ (Token validation data)
356
+ // 6. shutdown_mutex_ (Lowest priority - can be locked independently)
286
357
  //
287
358
  // Routing table (k-buckets)
288
359
  std::vector<std::vector<DhtNode>> routing_table_;
@@ -301,17 +372,6 @@ private:
301
372
  std::mutex peer_tokens_mutex_; // Lock order: 6
302
373
 
303
374
 
304
- // Pending announce tracking (for BEP 5 compliance)
305
- struct PendingAnnounce {
306
- InfoHash info_hash;
307
- uint16_t port;
308
- std::chrono::steady_clock::time_point created_at;
309
-
310
- PendingAnnounce(const InfoHash& hash, uint16_t p)
311
- : info_hash(hash), port(p), created_at(std::chrono::steady_clock::now()) {}
312
- };
313
- std::unordered_map<std::string, PendingAnnounce> pending_announces_;
314
- std::mutex pending_announces_mutex_; // Lock order: 4
315
375
 
316
376
  // Pending find_peers tracking (to map transaction IDs to info_hash)
317
377
  struct PendingSearch {
@@ -332,9 +392,16 @@ private:
332
392
  // Callbacks to invoke when peers are found (supports multiple concurrent searches for same info_hash)
333
393
  std::vector<PeerDiscoveryCallback> callbacks;
334
394
 
395
+ // Announce support: tokens collected during traversal (BEP 5 compliant)
396
+ // Maps node_id -> write_token received from that node
397
+ std::unordered_map<NodeId, std::string> write_tokens;
398
+ bool is_announce; // true if this search is for announce_peer
399
+ uint16_t announce_port; // port to announce (only valid if is_announce)
400
+
335
401
  PendingSearch(const InfoHash& hash)
336
402
  : info_hash(hash), created_at(std::chrono::steady_clock::now()),
337
- invoke_count(0), branch_factor(ALPHA), is_finished(false) {}
403
+ invoke_count(0), branch_factor(ALPHA), is_finished(false),
404
+ is_announce(false), announce_port(0) {}
338
405
  };
339
406
  std::unordered_map<std::string, PendingSearch> pending_searches_; // info_hash (hex) -> PendingSearch
340
407
  mutable std::mutex pending_searches_mutex_; // Lock order: 2
@@ -391,6 +458,14 @@ private:
391
458
  std::condition_variable shutdown_cv_;
392
459
  std::mutex shutdown_mutex_; // Lock order: 7 (can be locked independently)
393
460
 
461
+ #ifdef RATS_SEARCH_FEATURES
462
+ // Spider mode state
463
+ std::atomic<bool> spider_mode_{false};
464
+ std::atomic<bool> spider_ignore_{false}; // When true, ignore incoming requests
465
+ SpiderAnnounceCallback spider_announce_callback_;
466
+ std::mutex spider_callbacks_mutex_; // Protects spider callbacks
467
+ #endif // RATS_SEARCH_FEATURES
468
+
394
469
  // Helper functions
395
470
  void network_loop();
396
471
  void maintenance_loop();
@@ -414,7 +489,7 @@ private:
414
489
  void send_krpc_get_peers(const Peer& peer, const InfoHash& info_hash);
415
490
  void send_krpc_announce_peer(const Peer& peer, const InfoHash& info_hash, uint16_t port, const std::string& token);
416
491
 
417
- void add_node(const DhtNode& node, bool confirmed = true);
492
+ void add_node(const DhtNode& node, bool confirmed = true, bool no_verify = false);
418
493
  std::vector<DhtNode> find_closest_nodes(const NodeId& target, size_t count = K_BUCKET_SIZE);
419
494
  std::vector<DhtNode> find_closest_nodes_unlocked(const NodeId& target, size_t count = K_BUCKET_SIZE);
420
495
  int get_bucket_index(const NodeId& id);
@@ -434,10 +509,6 @@ private:
434
509
  void refresh_buckets();
435
510
  void print_statistics();
436
511
 
437
- // Pending announce management
438
- void cleanup_stale_announces();
439
- void handle_get_peers_response_for_announce(const std::string& transaction_id, const Peer& responder, const std::string& token);
440
-
441
512
  // Pending search management
442
513
  void cleanup_stale_searches();
443
514
  void cleanup_timed_out_search_requests();
@@ -445,8 +516,10 @@ private:
445
516
  void handle_get_peers_response_for_search(const std::string& transaction_id, const Peer& responder, const std::vector<Peer>& peers);
446
517
  void handle_get_peers_response_with_nodes(const std::string& transaction_id, const Peer& responder, const std::vector<KrpcNode>& nodes);
447
518
  void handle_get_peers_empty_response(const std::string& transaction_id, const Peer& responder);
519
+ void save_write_token(PendingSearch& search, const NodeId& node_id, const std::string& token);
448
520
  bool add_search_requests(PendingSearch& search, DeferredCallbacks& deferred);
449
521
  void add_node_to_search(PendingSearch& search, const DhtNode& node);
522
+ void send_announce_to_closest_nodes(PendingSearch& search);
450
523
 
451
524
  // Peer announcement storage management
452
525
  void store_announced_peer(const InfoHash& info_hash, const Peer& peer);
@@ -477,8 +477,8 @@ StunAddress NatTypeDetector::get_mapped_address_different_port(const std::string
477
477
 
478
478
  IceAgent::IceAgent(IceRole role, const IceConfig& config)
479
479
  : role_(role), config_(config), running_(false), state_(IceConnectionState::NEW),
480
- udp_socket_(INVALID_SOCKET_VALUE), tcp_socket_(INVALID_SOCKET_VALUE),
481
- selected_pair_(IceCandidate(), IceCandidate()) {
480
+ selected_pair_(IceCandidate(), IceCandidate()),
481
+ udp_socket_(INVALID_SOCKET_VALUE), tcp_socket_(INVALID_SOCKET_VALUE) {
482
482
 
483
483
  // Initialize NAT detector
484
484
  nat_detector_ = std::make_unique<NatTypeDetector>();
@@ -52,15 +52,15 @@ RatsClient::RatsClient(int listen_port, int max_peers, const NatTraversalConfig&
52
52
  : listen_port_(listen_port),
53
53
  bind_address_(bind_address),
54
54
  max_peers_(max_peers),
55
- nat_config_(nat_config),
56
55
  server_socket_(INVALID_SOCKET_VALUE),
57
56
  running_(false),
58
- auto_discovery_running_(false),
57
+ nat_config_(nat_config),
58
+ data_directory_("."),
59
59
  encryption_enabled_(false),
60
60
  detected_nat_type_(NatType::UNKNOWN),
61
- data_directory_("."),
62
61
  custom_protocol_name_("rats"),
63
- custom_protocol_version_("1.0") {
62
+ custom_protocol_version_("1.0"),
63
+ auto_discovery_running_(false) {
64
64
  // Initialize STUN client
65
65
  stun_client_ = std::make_unique<StunClient>();
66
66
 
@@ -1817,7 +1817,8 @@ bool RatsClient::find_peers_by_hash(const std::string& content_hash, std::functi
1817
1817
  });
1818
1818
  }
1819
1819
 
1820
- bool RatsClient::announce_for_hash(const std::string& content_hash, uint16_t port) {
1820
+ bool RatsClient::announce_for_hash(const std::string& content_hash, uint16_t port,
1821
+ std::function<void(const std::vector<std::string>&)> callback) {
1821
1822
  if (!dht_client_ || !dht_client_->is_running()) {
1822
1823
  LOG_CLIENT_ERROR("DHT client not running");
1823
1824
  return false;
@@ -1832,10 +1833,25 @@ bool RatsClient::announce_for_hash(const std::string& content_hash, uint16_t por
1832
1833
  port = listen_port_;
1833
1834
  }
1834
1835
 
1835
- LOG_CLIENT_INFO("Announcing for content hash: " << content_hash << " on port " << port);
1836
+ LOG_CLIENT_INFO("Announcing for content hash: " << content_hash << " on port " << port
1837
+ << (callback ? " with peer callback" : ""));
1836
1838
 
1837
1839
  InfoHash info_hash = hex_to_node_id(content_hash);
1838
- return dht_client_->announce_peer(info_hash, port);
1840
+
1841
+ // Create wrapper callback that converts Peer to string addresses (if callback provided)
1842
+ PeerDiscoveryCallback peer_callback = nullptr;
1843
+ if (callback) {
1844
+ peer_callback = [callback](const std::vector<Peer>& peers, const InfoHash& hash) {
1845
+ std::vector<std::string> peer_addresses;
1846
+ peer_addresses.reserve(peers.size());
1847
+ for (const auto& peer : peers) {
1848
+ peer_addresses.push_back(peer.ip + ":" + std::to_string(peer.port));
1849
+ }
1850
+ callback(peer_addresses);
1851
+ };
1852
+ }
1853
+
1854
+ return dht_client_->announce_peer(info_hash, port, peer_callback);
1839
1855
  }
1840
1856
 
1841
1857
  bool RatsClient::is_dht_running() const {
@@ -1931,46 +1947,23 @@ void RatsClient::automatic_discovery_loop() {
1931
1947
  }
1932
1948
  }
1933
1949
 
1934
- // Search immediately
1935
- search_rats_peers();
1936
-
1937
- {
1938
- std::unique_lock<std::mutex> lock(shutdown_mutex_);
1939
- if (shutdown_cv_.wait_for(lock, std::chrono::seconds(10), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
1940
- LOG_CLIENT_INFO("Automatic peer discovery loop stopped during search delay");
1941
- return;
1942
- }
1943
- }
1944
-
1945
- // Announce immediately
1950
+ // Announce immediately - this also discovers peers during traversal
1946
1951
  announce_rats_peer();
1947
1952
 
1948
1953
  auto last_announce = std::chrono::steady_clock::now();
1949
- auto last_search = std::chrono::steady_clock::now();
1950
1954
 
1951
1955
  while (auto_discovery_running_.load()) {
1952
1956
  auto now = std::chrono::steady_clock::now();
1953
1957
 
1954
- if (get_peer_count() == 0) {
1955
- // No peers: aggressive search and announce
1956
- if (now - last_search >= std::chrono::seconds(5)) {
1957
- search_rats_peers();
1958
- last_search = now;
1959
- }
1960
- if (now - last_announce >= std::chrono::seconds(20)) {
1961
- announce_rats_peer();
1962
- last_announce = now;
1963
- }
1964
- } else {
1965
- // Peers connected: less aggressive, similar to original logic
1966
- if (now - last_search >= std::chrono::minutes(5)) {
1967
- search_rats_peers();
1968
- last_search = now;
1969
- }
1970
- if (now - last_announce >= std::chrono::minutes(10)) {
1971
- announce_rats_peer();
1972
- last_announce = now;
1973
- }
1958
+ // Announce combines both announcing our presence and discovering peers
1959
+ // Adjust frequency based on whether we have peers
1960
+ auto interval = (get_peer_count() == 0)
1961
+ ? std::chrono::seconds(15) // Aggressive when no peers
1962
+ : std::chrono::minutes(10); // Less aggressive when connected
1963
+
1964
+ if (now - last_announce >= interval) {
1965
+ announce_rats_peer();
1966
+ last_announce = now;
1974
1967
  }
1975
1968
 
1976
1969
  // Use conditional variable for responsive shutdown
@@ -1994,8 +1987,35 @@ void RatsClient::announce_rats_peer() {
1994
1987
  std::string discovery_hash = get_discovery_hash();
1995
1988
  LOG_CLIENT_INFO("Announcing peer for discovery hash: " << discovery_hash << " on port " << listen_port_);
1996
1989
 
1997
- if (announce_for_hash(discovery_hash, listen_port_)) {
1998
- LOG_CLIENT_DEBUG("Successfully announced peer for discovery");
1990
+ InfoHash info_hash = hex_to_node_id(discovery_hash);
1991
+
1992
+ if (dht_client_->is_announce_active(info_hash)) {
1993
+ LOG_CLIENT_WARN("Announce already in progress for info hash: " << node_id_to_hex(info_hash));
1994
+ return;
1995
+ }
1996
+
1997
+ // Use announce with callback - combines announce and find_peers in one traversal
1998
+ // Peers discovered during traversal will be returned through the callback
1999
+ if (announce_for_hash(discovery_hash, listen_port_, [this, info_hash](const std::vector<std::string>& peer_addresses) {
2000
+ LOG_CLIENT_INFO("Announce discovered " << peer_addresses.size() << " peers during traversal");
2001
+
2002
+ // Convert peer addresses to Peer objects for handle_dht_peer_discovery()
2003
+ std::vector<Peer> peers;
2004
+ peers.reserve(peer_addresses.size());
2005
+ for (const auto& peer_address : peer_addresses) {
2006
+ std::string ip;
2007
+ int port;
2008
+ if (parse_address_string(peer_address, ip, port)) {
2009
+ peers.push_back(Peer(ip, port));
2010
+ }
2011
+ }
2012
+
2013
+ // Auto-connect to discovered peers
2014
+ if (!peers.empty()) {
2015
+ handle_dht_peer_discovery(peers, info_hash);
2016
+ }
2017
+ })) {
2018
+ LOG_CLIENT_DEBUG("Successfully started announce with peer discovery for discovery hash");
1999
2019
  } else {
2000
2020
  LOG_CLIENT_WARN("Failed to announce peer for discovery");
2001
2021
  }
@@ -606,12 +606,15 @@ public:
606
606
  std::function<void(const std::vector<std::string>&)> callback);
607
607
 
608
608
  /**
609
- * Announce our presence for a content hash
609
+ * Announce our presence for a content hash with optional peer discovery callback
610
+ * If callback is provided, peers discovered during DHT traversal will be returned through it
610
611
  * @param content_hash Hash to announce for (40-character hex string)
611
612
  * @param port Port to announce (default: our listen port)
613
+ * @param callback Optional function to call with discovered peers during traversal
612
614
  * @return true if announced successfully
613
615
  */
614
- bool announce_for_hash(const std::string& content_hash, uint16_t port = 0);
616
+ bool announce_for_hash(const std::string& content_hash, uint16_t port = 0,
617
+ std::function<void(const std::vector<std::string>&)> callback = nullptr);
615
618
 
616
619
  /**
617
620
  * Check if DHT is currently running
@@ -386,13 +386,29 @@ int rats_is_dht_running(rats_client_t handle) {
386
386
  return wrap->client->is_dht_running() ? 1 : 0;
387
387
  }
388
388
 
389
- rats_error_t rats_announce_for_hash(rats_client_t handle, const char* content_hash, int port) {
389
+ rats_error_t rats_announce_for_hash(rats_client_t handle, const char* content_hash, int port,
390
+ rats_peers_found_cb callback, void* user_data) {
390
391
  if (!handle || !content_hash) return RATS_ERROR_INVALID_PARAMETER;
391
392
  if (strlen(content_hash) != 40) return RATS_ERROR_INVALID_PARAMETER; // SHA1 hash must be 40 chars
392
393
  rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
393
394
 
394
395
  uint16_t announce_port = (port <= 0) ? 0 : static_cast<uint16_t>(port);
395
- return wrap->client->announce_for_hash(std::string(content_hash), announce_port) ?
396
+
397
+ // Create C++ callback wrapper if C callback is provided
398
+ std::function<void(const std::vector<std::string>&)> cpp_callback = nullptr;
399
+ if (callback) {
400
+ cpp_callback = [callback, user_data](const std::vector<std::string>& peers) {
401
+ // Convert vector to C-style array
402
+ std::vector<const char*> c_peers;
403
+ c_peers.reserve(peers.size());
404
+ for (const auto& peer : peers) {
405
+ c_peers.push_back(peer.c_str());
406
+ }
407
+ callback(user_data, c_peers.data(), static_cast<int>(c_peers.size()));
408
+ };
409
+ }
410
+
411
+ return wrap->client->announce_for_hash(std::string(content_hash), announce_port, cpp_callback) ?
396
412
  RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
397
413
  }
398
414
 
@@ -82,6 +82,7 @@ typedef void (*rats_json_cb)(void* user_data, const char* peer_id, const char* j
82
82
  typedef void (*rats_disconnect_cb)(void* user_data, const char* peer_id);
83
83
  typedef void (*rats_peer_discovered_cb)(void* user_data, const char* host, int port, const char* service_name);
84
84
  typedef void (*rats_message_cb)(void* user_data, const char* peer_id, const char* message_data);
85
+ typedef void (*rats_peers_found_cb)(void* user_data, const char** peer_addresses, int count);
85
86
 
86
87
  // Peer configuration
87
88
  RATS_API rats_error_t rats_set_max_peers(rats_client_t client, int max_peers);
@@ -106,7 +107,8 @@ RATS_API int rats_broadcast_json(rats_client_t client, const char* json_str);
106
107
  RATS_API rats_error_t rats_start_dht_discovery(rats_client_t client, int dht_port);
107
108
  RATS_API void rats_stop_dht_discovery(rats_client_t client);
108
109
  RATS_API int rats_is_dht_running(rats_client_t client);
109
- RATS_API rats_error_t rats_announce_for_hash(rats_client_t client, const char* content_hash, int port);
110
+ RATS_API rats_error_t rats_announce_for_hash(rats_client_t client, const char* content_hash, int port,
111
+ rats_peers_found_cb callback, void* user_data);
110
112
  RATS_API size_t rats_get_dht_routing_table_size(rats_client_t client);
111
113
 
112
114
  // Automatic discovery
@@ -45,6 +45,11 @@ bool RatsClient::start_mdns_discovery(const std::string& service_instance_name,
45
45
  // Create mDNS client
46
46
  mdns_client_ = std::make_unique<MdnsClient>(instance_name, listen_port_);
47
47
 
48
+ // Set service type based on protocol name (e.g., "_rats-search._tcp.local.")
49
+ std::string protocol_name = get_protocol_name();
50
+ std::string service_type = "_" + protocol_name + "._tcp.local.";
51
+ mdns_client_->set_service_type(service_type);
52
+
48
53
  // Set service discovery callback
49
54
  mdns_client_->set_service_callback([this](const MdnsService& service, bool is_new) {
50
55
  handle_mdns_service_discovery(service, is_new);
@@ -25,6 +25,7 @@ namespace librats {
25
25
  MdnsClient::MdnsClient(const std::string& service_instance_name, uint16_t service_port)
26
26
  : service_instance_name_(service_instance_name),
27
27
  service_port_(service_port),
28
+ service_type_(LIBRATS_SERVICE_TYPE), // Default service type
28
29
  multicast_socket_(INVALID_SOCKET_VALUE),
29
30
  running_(false),
30
31
  announcing_(false),
@@ -279,6 +280,11 @@ void MdnsClient::set_query_interval(std::chrono::seconds interval) {
279
280
  query_interval_ = interval;
280
281
  }
281
282
 
283
+ void MdnsClient::set_service_type(const std::string& service_type) {
284
+ service_type_ = service_type;
285
+ LOG_MDNS_INFO("Service type set to: " << service_type_);
286
+ }
287
+
282
288
  bool MdnsClient::create_multicast_socket() {
283
289
  multicast_socket_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
284
290
  if (!librats::is_valid_socket(multicast_socket_)) {
@@ -540,7 +546,7 @@ void MdnsClient::process_query(const DnsMessage& query, const std::string& sende
540
546
  bool should_respond = false;
541
547
 
542
548
  // Check if the question is asking for our service type
543
- if (question.name == LIBRATS_SERVICE_TYPE && question.type == DnsRecordType::PTR) {
549
+ if (question.name == service_type_ && question.type == DnsRecordType::PTR) {
544
550
  should_respond = true;
545
551
  }
546
552
 
@@ -627,7 +633,7 @@ void MdnsClient::extract_service_from_response(const DnsMessage& response, const
627
633
  }
628
634
 
629
635
  bool MdnsClient::is_librats_service(const std::string& service_name) const {
630
- return service_name == LIBRATS_SERVICE_TYPE;
636
+ return service_name == service_type_;
631
637
  }
632
638
 
633
639
  void MdnsClient::add_or_update_service(const MdnsService& service) {
@@ -668,8 +674,8 @@ DnsMessage MdnsClient::create_query_message() {
668
674
  query.header.flags = static_cast<uint16_t>(MdnsFlags::QUERY);
669
675
  query.header.question_count = 1;
670
676
 
671
- // Add question for librats services
672
- DnsQuestion question(LIBRATS_SERVICE_TYPE, DnsRecordType::PTR, DnsRecordClass::CLASS_IN);
677
+ // Add question for services with our service type
678
+ DnsQuestion question(service_type_, DnsRecordType::PTR, DnsRecordClass::CLASS_IN);
673
679
  query.questions.push_back(question);
674
680
 
675
681
  return query;
@@ -689,7 +695,7 @@ DnsMessage MdnsClient::create_announcement_message() {
689
695
  }
690
696
 
691
697
  // Create PTR record
692
- DnsResourceRecord ptr_record = create_ptr_record(LIBRATS_SERVICE_TYPE, our_service_name);
698
+ DnsResourceRecord ptr_record = create_ptr_record(service_type_, our_service_name);
693
699
  announcement.answers.push_back(ptr_record);
694
700
 
695
701
  // Create SRV record
@@ -1120,11 +1126,11 @@ std::string MdnsClient::create_service_instance_name(const std::string& instance
1120
1126
  clean_name = "librats-node";
1121
1127
  }
1122
1128
 
1123
- return clean_name + "." + LIBRATS_SERVICE_TYPE;
1129
+ return clean_name + "." + service_type_;
1124
1130
  }
1125
1131
 
1126
1132
  std::string MdnsClient::extract_instance_name_from_service(const std::string& service_name) {
1127
- size_t pos = service_name.find("." + LIBRATS_SERVICE_TYPE);
1133
+ size_t pos = service_name.find("." + service_type_);
1128
1134
  if (pos != std::string::npos) {
1129
1135
  return service_name.substr(0, pos);
1130
1136
  }
@@ -152,11 +152,13 @@ public:
152
152
  // Configuration
153
153
  void set_announcement_interval(std::chrono::seconds interval);
154
154
  void set_query_interval(std::chrono::seconds interval);
155
+ void set_service_type(const std::string& service_type);
155
156
 
156
157
  private:
157
158
  // Core properties
158
159
  std::string service_instance_name_;
159
160
  uint16_t service_port_;
161
+ std::string service_type_; // Dynamic service type (e.g., "_rats-search._tcp.local.")
160
162
  std::map<std::string, std::string> txt_records_;
161
163
 
162
164
  // Network properties
@@ -10,7 +10,9 @@
10
10
  #ifdef _WIN32
11
11
  #include <windows.h>
12
12
  #include <bcrypt.h>
13
- #pragma comment(lib, "bcrypt.lib")
13
+ #ifdef _MSC_VER
14
+ #pragma comment(lib, "bcrypt.lib")
15
+ #endif
14
16
  #else
15
17
  #include <fcntl.h>
16
18
  #include <unistd.h>
@@ -11,7 +11,9 @@
11
11
  #include <sysinfoapi.h>
12
12
  #include <versionhelpers.h>
13
13
  #include <intrin.h>
14
- #pragma comment(lib, "version.lib")
14
+ #ifdef _MSC_VER
15
+ #pragma comment(lib, "version.lib")
16
+ #endif
15
17
  #elif __APPLE__
16
18
  #include <sys/utsname.h>
17
19
  #include <sys/sysctl.h>
@@ -8,7 +8,9 @@
8
8
  #ifdef _WIN32
9
9
  #include <winsock2.h>
10
10
  #include <ws2tcpip.h>
11
- #pragma comment(lib, "ws2_32.lib")
11
+ #ifdef _MSC_VER
12
+ #pragma comment(lib, "ws2_32.lib")
13
+ #endif
12
14
  typedef SOCKET socket_t;
13
15
  #define INVALID_SOCKET_VALUE INVALID_SOCKET
14
16
  #define SOCKET_ERROR_VALUE SOCKET_ERROR
@@ -106,7 +106,7 @@ std::vector<uint8_t> StunClient::create_binding_request() {
106
106
  std::mt19937 gen(rd());
107
107
  std::uniform_int_distribution<> dis(0, 255);
108
108
 
109
- for (int i = 0; i < stun::TRANSACTION_ID_SIZE; ++i) {
109
+ for (size_t i = 0; i < stun::TRANSACTION_ID_SIZE; ++i) {
110
110
  request[8 + i] = static_cast<uint8_t>(dis(gen));
111
111
  }
112
112
 
@@ -226,7 +226,7 @@ void StunClient::generate_transaction_id(uint8_t* transaction_id) {
226
226
  std::mt19937 gen(rd());
227
227
  std::uniform_int_distribution<> dis(0, 255);
228
228
 
229
- for (int i = 0; i < stun::TRANSACTION_ID_SIZE; ++i) {
229
+ for (size_t i = 0; i < stun::TRANSACTION_ID_SIZE; ++i) {
230
230
  transaction_id[i] = static_cast<uint8_t>(dis(gen));
231
231
  }
232
232
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "librats",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Node.js bindings for librats - A high-performance peer-to-peer networking library",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -473,7 +473,52 @@ private:
473
473
  std::string content_hash = info[0].As<Napi::String>().Utf8Value();
474
474
  int port = info[1].As<Napi::Number>().Int32Value();
475
475
 
476
- rats_error_t result = rats_announce_for_hash(client_, content_hash.c_str(), port);
476
+ // Check if optional callback is provided
477
+ rats_peers_found_cb c_callback = nullptr;
478
+ void* callback_user_data = nullptr;
479
+ Napi::ThreadSafeFunction* tsfn_ptr = nullptr;
480
+
481
+ if (info.Length() >= 3 && info[2].IsFunction()) {
482
+ // Create thread-safe function for callback
483
+ auto tsfn = new Napi::ThreadSafeFunction();
484
+ *tsfn = Napi::ThreadSafeFunction::New(
485
+ env,
486
+ info[2].As<Napi::Function>(),
487
+ "AnnounceForHashCallback",
488
+ 0,
489
+ 1,
490
+ [](Napi::Env) {} // Release callback
491
+ );
492
+ tsfn_ptr = tsfn;
493
+
494
+ c_callback = [](void* user_data, const char** peer_addresses, int count) {
495
+ auto* tsfn = static_cast<Napi::ThreadSafeFunction*>(user_data);
496
+ if (!tsfn) return;
497
+
498
+ // Copy peer addresses for async callback
499
+ std::vector<std::string>* peers = new std::vector<std::string>();
500
+ for (int i = 0; i < count; i++) {
501
+ if (peer_addresses[i]) {
502
+ peers->push_back(peer_addresses[i]);
503
+ }
504
+ }
505
+
506
+ tsfn->BlockingCall(peers, [](Napi::Env env, Napi::Function jsCallback, std::vector<std::string>* data) {
507
+ Napi::Array arr = Napi::Array::New(env, data->size());
508
+ for (size_t i = 0; i < data->size(); i++) {
509
+ arr.Set(static_cast<uint32_t>(i), Napi::String::New(env, (*data)[i]));
510
+ }
511
+ jsCallback.Call({arr});
512
+ delete data;
513
+ });
514
+
515
+ tsfn->Release();
516
+ delete tsfn;
517
+ };
518
+ callback_user_data = tsfn_ptr;
519
+ }
520
+
521
+ rats_error_t result = rats_announce_for_hash(client_, content_hash.c_str(), port, c_callback, callback_user_data);
477
522
  return Napi::Boolean::New(env, result == RATS_SUCCESS);
478
523
  }
479
524