librats 0.5.4 → 0.6.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.
Files changed (56) hide show
  1. package/lib/index.d.ts +210 -35
  2. package/native-src/CMakeLists.txt +71 -14
  3. package/native-src/LICENSE +1 -1
  4. package/native-src/src/bittorrent.cpp +153 -1
  5. package/native-src/src/bittorrent.h +25 -0
  6. package/native-src/src/crc32.cpp +97 -0
  7. package/native-src/src/crc32.h +129 -0
  8. package/native-src/src/crypto/blake2_endian.h +57 -0
  9. package/native-src/src/crypto/blake2b.c +202 -0
  10. package/native-src/src/crypto/blake2b.h +53 -0
  11. package/native-src/src/crypto/blake2s.c +266 -0
  12. package/native-src/src/crypto/blake2s.h +68 -0
  13. package/native-src/src/crypto/chacha.c +312 -0
  14. package/native-src/src/crypto/chacha.h +66 -0
  15. package/native-src/src/crypto/chachapoly.c +214 -0
  16. package/native-src/src/crypto/chachapoly.h +101 -0
  17. package/native-src/src/crypto/curve25519.c +863 -0
  18. package/native-src/src/crypto/curve25519.h +68 -0
  19. package/native-src/src/crypto/hkdf.c +266 -0
  20. package/native-src/src/crypto/hkdf.h +141 -0
  21. package/native-src/src/crypto/poly1305.c +566 -0
  22. package/native-src/src/crypto/poly1305.h +36 -0
  23. package/native-src/src/crypto/sha256.c +189 -0
  24. package/native-src/src/crypto/sha256.h +54 -0
  25. package/native-src/src/crypto/sha512.c +206 -0
  26. package/native-src/src/crypto/sha512.h +54 -0
  27. package/native-src/src/dht.cpp +305 -57
  28. package/native-src/src/dht.h +46 -4
  29. package/native-src/src/gossipsub.cpp +5 -3
  30. package/native-src/src/ice.cpp +654 -1147
  31. package/native-src/src/ice.h +492 -261
  32. package/native-src/src/librats.cpp +390 -516
  33. package/native-src/src/librats.h +502 -319
  34. package/native-src/src/librats_bittorrent.cpp +54 -0
  35. package/native-src/src/librats_c.cpp +341 -120
  36. package/native-src/src/librats_c.h +106 -26
  37. package/native-src/src/librats_encryption.cpp +365 -65
  38. package/native-src/src/librats_ice.cpp +146 -452
  39. package/native-src/src/librats_persistence.cpp +2 -21
  40. package/native-src/src/librats_storage.cpp +189 -0
  41. package/native-src/src/noise.cpp +628 -808
  42. package/native-src/src/noise.h +334 -154
  43. package/native-src/src/socket.cpp +73 -71
  44. package/native-src/src/socket.h +7 -0
  45. package/native-src/src/storage.cpp +1468 -0
  46. package/native-src/src/storage.h +541 -0
  47. package/native-src/src/stun.cpp +992 -352
  48. package/native-src/src/stun.h +450 -311
  49. package/native-src/src/turn.cpp +764 -0
  50. package/native-src/src/turn.h +460 -0
  51. package/package.json +1 -1
  52. package/scripts/prepare-package.js +2 -2
  53. package/src/librats_node.cpp +275 -92
  54. package/native-src/src/encrypted_socket.cpp +0 -817
  55. package/native-src/src/encrypted_socket.h +0 -239
  56. package/native-src/src/librats_nat.cpp +0 -571
package/lib/index.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * LibRats Node.js Bindings - TypeScript Definitions
3
3
  *
4
4
  * High-performance peer-to-peer networking library with support for DHT, GossipSub,
5
- * file transfer, NAT traversal, and more.
5
+ * file transfer, and more.
6
6
  */
7
7
 
8
8
  /**
@@ -15,22 +15,6 @@ export interface VersionInfo {
15
15
  build: number;
16
16
  }
17
17
 
18
- /**
19
- * Connection strategy options for connecting to peers
20
- */
21
- export enum ConnectionStrategy {
22
- /** Direct connection only, no NAT traversal */
23
- DIRECT_ONLY = 0,
24
- /** STUN-assisted connection */
25
- STUN_ASSISTED = 1,
26
- /** Full ICE negotiation with STUN and connection checks */
27
- ICE_FULL = 2,
28
- /** TURN relay connection */
29
- TURN_RELAY = 3,
30
- /** Automatic strategy selection based on network conditions */
31
- AUTO_ADAPTIVE = 4
32
- }
33
-
34
18
  /**
35
19
  * Error codes returned by various operations
36
20
  */
@@ -53,6 +37,39 @@ export enum ErrorCodes {
53
37
  JSON_PARSE = -7
54
38
  }
55
39
 
40
+ /**
41
+ * ICE connection states
42
+ */
43
+ export enum IceConnectionState {
44
+ NEW = 0,
45
+ GATHERING = 1,
46
+ CHECKING = 2,
47
+ CONNECTED = 3,
48
+ COMPLETED = 4,
49
+ FAILED = 5,
50
+ DISCONNECTED = 6,
51
+ CLOSED = 7
52
+ }
53
+
54
+ /**
55
+ * ICE gathering states
56
+ */
57
+ export enum IceGatheringState {
58
+ NEW = 0,
59
+ GATHERING = 1,
60
+ COMPLETE = 2
61
+ }
62
+
63
+ /**
64
+ * ICE candidate types
65
+ */
66
+ export enum IceCandidateType {
67
+ HOST = 0,
68
+ SRFLX = 1,
69
+ PRFLX = 2,
70
+ RELAY = 3
71
+ }
72
+
56
73
  /**
57
74
  * Main RatsClient class for peer-to-peer networking
58
75
  */
@@ -84,15 +101,6 @@ export class RatsClient {
84
101
  */
85
102
  connect(host: string, port: number): boolean;
86
103
 
87
- /**
88
- * Connect to a peer with a specific connection strategy
89
- * @param host - IP address or hostname of the peer
90
- * @param port - Port number of the peer
91
- * @param strategy - Connection strategy to use
92
- * @returns true if connection initiated successfully
93
- */
94
- connectWithStrategy(host: string, port: number, strategy: ConnectionStrategy): boolean;
95
-
96
104
  /**
97
105
  * Disconnect from a peer
98
106
  * @param peerId - ID of the peer to disconnect from
@@ -101,6 +109,12 @@ export class RatsClient {
101
109
 
102
110
  // ============ Information ============
103
111
 
112
+ /**
113
+ * Get the port the client is listening on
114
+ * @returns Listen port number
115
+ */
116
+ getListenPort(): number;
117
+
104
118
  /**
105
119
  * Get the number of connected peers
106
120
  * @returns Number of connected peers
@@ -276,6 +290,13 @@ export class RatsClient {
276
290
  */
277
291
  resumeFileTransfer(transferId: string): boolean;
278
292
 
293
+ /**
294
+ * Get file transfer progress information as JSON string
295
+ * @param transferId - ID of the transfer to query
296
+ * @returns JSON string with progress info, or null if not found
297
+ */
298
+ getFileTransferProgress(transferId: string): string | null;
299
+
279
300
  // ============ GossipSub ============
280
301
 
281
302
  /**
@@ -340,6 +361,12 @@ export class RatsClient {
340
361
  */
341
362
  getTopicPeers(topic: string): string[];
342
363
 
364
+ /**
365
+ * Get GossipSub statistics as JSON string
366
+ * @returns JSON string with statistics, or null if unavailable
367
+ */
368
+ getGossipsubStatistics(): string | null;
369
+
343
370
  // ============ DHT ============
344
371
 
345
372
  /**
@@ -395,6 +422,20 @@ export class RatsClient {
395
422
  */
396
423
  isMdnsRunning(): boolean;
397
424
 
425
+ /**
426
+ * Query for mDNS services
427
+ * @returns true if query sent successfully
428
+ */
429
+ queryMdnsServices(): boolean;
430
+
431
+ // ============ Address Blocking ============
432
+
433
+ /**
434
+ * Add an IP address to the ignore list
435
+ * @param ipAddress - IP address to ignore
436
+ */
437
+ addIgnoredAddress(ipAddress: string): void;
438
+
398
439
  // ============ Encryption ============
399
440
 
400
441
  /**
@@ -411,23 +452,157 @@ export class RatsClient {
411
452
  isEncryptionEnabled(): boolean;
412
453
 
413
454
  /**
414
- * Generate a new encryption key
415
- * @returns Hex-encoded encryption key, or null if failed
455
+ * Initialize encryption system
456
+ * @param enable - Whether to enable encryption
457
+ * @returns true if initialized successfully
416
458
  */
417
- generateEncryptionKey(): string | null;
459
+ initializeEncryption(enable: boolean): boolean;
418
460
 
419
461
  /**
420
- * Set the encryption key
421
- * @param keyHex - Hex-encoded encryption key
462
+ * Check if a specific peer connection is encrypted
463
+ * @param peerId - Peer ID to check
464
+ * @returns true if peer connection is encrypted
465
+ */
466
+ isPeerEncrypted(peerId: string): boolean;
467
+
468
+ /**
469
+ * Set custom Noise Protocol static keypair
470
+ * @param privateKeyHex - 32-byte private key as 64-char hex string
422
471
  * @returns true if set successfully
423
472
  */
424
- setEncryptionKey(keyHex: string): boolean;
473
+ setNoiseStaticKeypair(privateKeyHex: string): boolean;
474
+
475
+ /**
476
+ * Get our Noise Protocol static public key
477
+ * @returns 64-char hex string, or null if not available
478
+ */
479
+ getNoiseStaticPublicKey(): string | null;
480
+
481
+ /**
482
+ * Get remote peer's Noise static public key
483
+ * @param peerId - Peer ID to query
484
+ * @returns 64-char hex string, or null if not available
485
+ */
486
+ getPeerNoisePublicKey(peerId: string): string | null;
487
+
488
+ /**
489
+ * Get handshake hash for channel binding
490
+ * @param peerId - Peer ID to query
491
+ * @returns 64-char hex string, or null if not available
492
+ */
493
+ getPeerHandshakeHash(peerId: string): string | null;
494
+
495
+ // ============ ICE (NAT Traversal) ============
496
+
497
+ /**
498
+ * Check if ICE is available
499
+ * @returns true if ICE is available
500
+ */
501
+ isIceAvailable(): boolean;
502
+
503
+ /**
504
+ * Add a STUN server for NAT traversal
505
+ * @param host - STUN server hostname or IP
506
+ * @param port - STUN server port (default: 3478)
507
+ */
508
+ addStunServer(host: string, port?: number): void;
509
+
510
+ /**
511
+ * Add a TURN server for relay-based NAT traversal
512
+ * @param host - TURN server hostname or IP
513
+ * @param port - TURN server port
514
+ * @param username - TURN username
515
+ * @param password - TURN password
516
+ */
517
+ addTurnServer(host: string, port: number, username: string, password: string): void;
518
+
519
+ /**
520
+ * Clear all ICE (STUN/TURN) servers
521
+ */
522
+ clearIceServers(): void;
523
+
524
+ /**
525
+ * Start gathering ICE candidates
526
+ * @returns true if gathering started successfully
527
+ */
528
+ gatherIceCandidates(): boolean;
529
+
530
+ /**
531
+ * Get local ICE candidates as JSON string
532
+ * @returns JSON string of candidates, or null if unavailable
533
+ */
534
+ getIceCandidates(): string | null;
535
+
536
+ /**
537
+ * Check if ICE candidate gathering is complete
538
+ * @returns true if gathering is complete
539
+ */
540
+ isIceGatheringComplete(): boolean;
541
+
542
+ /**
543
+ * Get public address discovered via STUN
544
+ * @returns Address string (ip:port), or null if not discovered
545
+ */
546
+ getPublicAddress(): string | null;
547
+
548
+ /**
549
+ * Perform a simple STUN binding request to discover public address
550
+ * @param stunServer - STUN server hostname (default: "stun.l.google.com")
551
+ * @param port - STUN server port (default: 19302)
552
+ * @param timeoutMs - Timeout in milliseconds (default: 5000)
553
+ * @returns Address string (ip:port), or null on failure
554
+ */
555
+ discoverPublicAddress(stunServer?: string, port?: number, timeoutMs?: number): string | null;
556
+
557
+ /**
558
+ * Add a remote ICE candidate from SDP
559
+ * @param candidateSdp - SDP candidate string
560
+ */
561
+ addRemoteIceCandidate(candidateSdp: string): void;
562
+
563
+ /**
564
+ * Signal end of remote ICE candidates (trickle ICE complete)
565
+ */
566
+ endOfRemoteIceCandidates(): void;
567
+
568
+ /**
569
+ * Start ICE connectivity checks
570
+ */
571
+ startIceChecks(): void;
572
+
573
+ /**
574
+ * Get current ICE connection state
575
+ * @returns ICE connection state value
576
+ */
577
+ getIceConnectionState(): number;
578
+
579
+ /**
580
+ * Get ICE gathering state
581
+ * @returns ICE gathering state value
582
+ */
583
+ getIceGatheringState(): number;
584
+
585
+ /**
586
+ * Check if ICE is connected
587
+ * @returns true if ICE connection is established
588
+ */
589
+ isIceConnected(): boolean;
590
+
591
+ /**
592
+ * Get the selected ICE candidate pair as JSON string
593
+ * @returns JSON string of selected pair, or null if unavailable
594
+ */
595
+ getIceSelectedPair(): string | null;
596
+
597
+ /**
598
+ * Close ICE manager and release resources
599
+ */
600
+ closeIce(): void;
425
601
 
426
602
  /**
427
- * Get the current encryption key
428
- * @returns Hex-encoded encryption key, or null if not set
603
+ * Restart ICE (re-gather candidates and restart checks)
429
604
  */
430
- getEncryptionKey(): string | null;
605
+ restartIce(): void;
431
606
 
432
607
  // ============ Configuration Persistence ============
433
608
 
@@ -82,6 +82,7 @@ option(RATS_CROSSCOMPILING "Force cross-compilation flags" OFF)
82
82
  option(RATS_SHARED_LIBRARY "Build as shared library" OFF)
83
83
  option(RATS_STATIC_LIBRARY "Build as static library" ON)
84
84
  option(RATS_SEARCH_FEATURES "Features related to rats-search project (like bittorrent)" OFF)
85
+ option(RATS_STORAGE "Enable distributed storage module" OFF)
85
86
 
86
87
  # Validate library type options
87
88
  if(RATS_SHARED_LIBRARY AND RATS_STATIC_LIBRARY)
@@ -128,30 +129,20 @@ set(LIBRARY_SOURCES
128
129
  src/krpc.h
129
130
  src/librats.cpp
130
131
  src/librats_logging.cpp
132
+ src/librats_encryption.cpp
131
133
  src/librats_file_transfer.cpp
132
134
  src/librats_gossipsub.cpp
133
- src/librats_nat.cpp
134
- src/librats_ice.cpp
135
135
  src/librats_mdns.cpp
136
136
  src/librats_persistence.cpp
137
- src/librats_encryption.cpp
138
137
  src/librats.h
139
138
  src/sha1.cpp
140
139
  src/sha1.h
141
140
  src/os.cpp
142
141
  src/os.h
143
- src/stun.cpp
144
- src/stun.h
145
- src/ice.cpp
146
- src/ice.h
147
142
  src/fs.cpp
148
143
  src/fs.h
149
144
  src/logger.h
150
145
  src/logger.cpp
151
- src/noise.cpp
152
- src/noise.h
153
- src/encrypted_socket.cpp
154
- src/encrypted_socket.h
155
146
  src/mdns.cpp
156
147
  src/mdns.h
157
148
  src/threadmanager.cpp
@@ -163,6 +154,40 @@ set(LIBRARY_SOURCES
163
154
  src/version.cpp
164
155
  src/rats_export.h
165
156
  ${PROJECT_BINARY_DIR}/src/version.h
157
+
158
+ # Cypto algorithms including Noise Protocol needed
159
+ src/crypto/curve25519.c
160
+ src/crypto/curve25519.h
161
+ src/crypto/chacha.c
162
+ src/crypto/chacha.h
163
+ src/crypto/poly1305.c
164
+ src/crypto/poly1305.h
165
+ src/crypto/chachapoly.c
166
+ src/crypto/chachapoly.h
167
+ src/crypto/sha256.c
168
+ src/crypto/sha256.h
169
+ src/crypto/sha512.c
170
+ src/crypto/sha512.h
171
+ src/crypto/hkdf.c
172
+ src/crypto/hkdf.h
173
+ src/crypto/blake2s.c
174
+ src/crypto/blake2s.h
175
+ src/crypto/blake2b.c
176
+ src/crypto/blake2b.h
177
+ src/crypto/blake2_endian.h
178
+
179
+ # Noise Protocol implementation
180
+ src/noise.cpp
181
+ src/noise.h
182
+
183
+ # NAT traversal (STUN/TURN/ICE)
184
+ src/stun.cpp
185
+ src/stun.h
186
+ src/turn.cpp
187
+ src/turn.h
188
+ src/ice.cpp
189
+ src/ice.h
190
+ src/librats_ice.cpp
166
191
  )
167
192
 
168
193
  # Add BitTorrent sources if RATS_SEARCH_FEATURES is enabled
@@ -176,6 +201,17 @@ if(RATS_SEARCH_FEATURES)
176
201
  )
177
202
  endif()
178
203
 
204
+ # Add Storage sources if RATS_STORAGE is enabled
205
+ if(RATS_STORAGE)
206
+ list(APPEND LIBRARY_SOURCES
207
+ src/crc32.cpp
208
+ src/crc32.h
209
+ src/storage.cpp
210
+ src/storage.h
211
+ src/librats_storage.cpp
212
+ )
213
+ endif()
214
+
179
215
  if(RATS_BINDINGS)
180
216
  list(APPEND LIBRARY_SOURCES
181
217
  src/librats_c.cpp
@@ -200,6 +236,7 @@ set_target_properties(rats PROPERTIES POSITION_INDEPENDENT_CODE ON)
200
236
 
201
237
  # Include directories
202
238
  target_include_directories(rats PUBLIC ${PROJECT_BINARY_DIR}/src src)
239
+ target_include_directories(rats PUBLIC ${PROJECT_SOURCE_DIR}/src/crypto)
203
240
 
204
241
  # Find and link threading support
205
242
  find_package(Threads REQUIRED)
@@ -235,6 +272,11 @@ if(RATS_SEARCH_FEATURES)
235
272
  target_compile_definitions(rats PUBLIC RATS_SEARCH_FEATURES)
236
273
  endif(RATS_SEARCH_FEATURES)
237
274
 
275
+ if(RATS_STORAGE)
276
+ message(STATUS "Enable distributed storage module")
277
+ target_compile_definitions(rats PUBLIC RATS_STORAGE)
278
+ endif(RATS_STORAGE)
279
+
238
280
  # Create the main executable
239
281
  if(RATS_BUILD_EXAMPLES)
240
282
  add_executable(rats-client src/main.cpp)
@@ -311,16 +353,23 @@ if(RATS_BUILD_TESTS)
311
353
  tests/test_dht.cpp
312
354
  tests/test_rats_client.cpp
313
355
  tests/test_os.cpp
314
- tests/test_stun.cpp
315
- tests/test_ice.cpp
316
356
  tests/test_fs.cpp
317
357
  tests/test_config_persistence.cpp
318
358
  tests/test_main.cpp
319
359
  tests/test_message_exchange.cpp
320
- tests/test_noise.cpp
321
360
  tests/test_gossipsub.cpp
322
361
  tests/test_logging_api_gtest.cpp
323
362
  tests/test_file_transfer.cpp
363
+ # Noise Protocol Crypto tests
364
+ tests/test_crypto_curve25519.cpp
365
+ tests/test_crypto_chacha_poly.cpp
366
+ tests/test_crypto_sha2.cpp
367
+ tests/test_crypto_blake2.cpp
368
+ tests/test_noise.cpp
369
+ # NAT traversal tests (STUN/TURN/ICE)
370
+ tests/test_stun.cpp
371
+ tests/test_turn.cpp
372
+ tests/test_ice.cpp
324
373
  )
325
374
 
326
375
  # Add BitTorrent tests if RATS_SEARCH_FEATURES is enabled
@@ -331,6 +380,14 @@ if(RATS_BUILD_TESTS)
331
380
  )
332
381
  endif()
333
382
 
383
+ # Add Storage tests if RATS_STORAGE is enabled
384
+ if(RATS_STORAGE)
385
+ list(APPEND TEST_SOURCES
386
+ tests/test_crc32.cpp
387
+ tests/test_storage.cpp
388
+ )
389
+ endif()
390
+
334
391
  if(RATS_BINDINGS)
335
392
  list(APPEND TEST_SOURCES
336
393
  tests/test_librats_c_api.cpp
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 librats contributors
3
+ Copyright (c) 2026 librats contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -487,12 +487,15 @@ void PeerConnection::connection_loop() {
487
487
  // Create socket if not already provided (outgoing connection)
488
488
  // This is done in the thread to avoid blocking the caller
489
489
  if (!is_valid_socket(socket_)) {
490
+ LOG_BT_DEBUG("Creating TCP connection to " << peer_info_.ip << ":" << peer_info_.port << " (5s timeout)");
490
491
  socket_ = create_tcp_client(peer_info_.ip, peer_info_.port, 5000); // 5-second timeout for faster failure
491
492
  if (!is_valid_socket(socket_)) {
492
- LOG_BT_ERROR("Failed to create connection to " << peer_info_.ip << ":" << peer_info_.port);
493
+ LOG_BT_ERROR("Failed to create connection to " << peer_info_.ip << ":" << peer_info_.port
494
+ << " - connection timed out or refused");
493
495
  state_.store(PeerState::ERROR);
494
496
  return;
495
497
  }
498
+ LOG_BT_DEBUG("TCP connection established to " << peer_info_.ip << ":" << peer_info_.port);
496
499
  }
497
500
 
498
501
  // Perform handshake only if not already completed (for outgoing connections)
@@ -3566,6 +3569,155 @@ void BitTorrentClient::get_torrent_metadata_by_hash(const std::string& info_hash
3566
3569
  get_torrent_metadata_by_hash(info_hash, callback);
3567
3570
  }
3568
3571
 
3572
+ void BitTorrentClient::get_torrent_metadata_from_peer(const InfoHash& info_hash,
3573
+ const std::string& peer_ip,
3574
+ uint16_t peer_port,
3575
+ MetadataRetrievalCallback callback) {
3576
+ if (!running_.load()) {
3577
+ LOG_BT_ERROR("BitTorrent client is not running");
3578
+ if (callback) {
3579
+ callback(TorrentInfo(), false, "BitTorrent client is not running");
3580
+ }
3581
+ return;
3582
+ }
3583
+
3584
+ LOG_BT_INFO("Retrieving metadata for hash " << info_hash_to_hex(info_hash)
3585
+ << " directly from peer " << peer_ip << ":" << peer_port);
3586
+
3587
+ // Check if metadata retrieval already in progress for this hash
3588
+ {
3589
+ std::lock_guard<std::mutex> lock(metadata_mutex_);
3590
+ if (metadata_only_torrents_.find(info_hash) != metadata_only_torrents_.end()) {
3591
+ LOG_BT_DEBUG("Metadata retrieval already in progress for hash: " << info_hash_to_hex(info_hash));
3592
+ // Just add this peer to the existing download attempt
3593
+ auto it = metadata_only_torrents_.find(info_hash);
3594
+ if (it != metadata_only_torrents_.end()) {
3595
+ Peer peer{peer_ip, peer_port};
3596
+ it->second->add_peer(peer);
3597
+ LOG_BT_INFO("Added peer " << peer_ip << ":" << peer_port << " to existing metadata download");
3598
+ }
3599
+ return;
3600
+ }
3601
+
3602
+ // Store the callback
3603
+ metadata_retrieval_callbacks_[info_hash] = callback;
3604
+ }
3605
+
3606
+ // Create a minimal TorrentInfo for metadata exchange
3607
+ TorrentInfo metadata_torrent_info = TorrentInfo::create_for_metadata_exchange(info_hash);
3608
+
3609
+ // Create a temporary torrent download for metadata exchange only
3610
+ auto metadata_torrent = std::make_shared<TorrentDownload>(metadata_torrent_info, ""); // No download path needed
3611
+
3612
+ // Set up metadata completion callback
3613
+ // IMPORTANT: This callback is invoked from within a PeerConnection's thread, so we must NOT
3614
+ // call stop() directly (which would try to join the calling thread, causing a deadlock).
3615
+ // Instead, we defer the cleanup to a detached thread.
3616
+ metadata_torrent->set_metadata_complete_callback([this, info_hash](const TorrentInfo& torrent_info) {
3617
+ LOG_BT_INFO("Metadata retrieval (direct) completed for " << torrent_info.get_name());
3618
+
3619
+ // Capture torrent_info by value since we're deferring to another thread
3620
+ TorrentInfo captured_info = torrent_info;
3621
+
3622
+ // Defer stop() and cleanup to a separate thread to avoid deadlock
3623
+ std::thread([this, info_hash, captured_info]() {
3624
+ MetadataRetrievalCallback user_callback;
3625
+ std::shared_ptr<TorrentDownload> temp_torrent;
3626
+
3627
+ // Get callback and cleanup
3628
+ {
3629
+ std::lock_guard<std::mutex> lock(metadata_mutex_);
3630
+ auto it = metadata_retrieval_callbacks_.find(info_hash);
3631
+ if (it != metadata_retrieval_callbacks_.end()) {
3632
+ user_callback = it->second;
3633
+ metadata_retrieval_callbacks_.erase(it);
3634
+ }
3635
+
3636
+ // Get temporary torrent to stop it
3637
+ auto torrent_it = metadata_only_torrents_.find(info_hash);
3638
+ if (torrent_it != metadata_only_torrents_.end()) {
3639
+ temp_torrent = torrent_it->second;
3640
+ metadata_only_torrents_.erase(torrent_it);
3641
+ }
3642
+ }
3643
+
3644
+ // Stop the temporary torrent
3645
+ if (temp_torrent) {
3646
+ temp_torrent->stop();
3647
+ }
3648
+
3649
+ // Call user callback with success
3650
+ if (user_callback) {
3651
+ user_callback(captured_info, true, "");
3652
+ }
3653
+ }).detach();
3654
+ });
3655
+
3656
+ // Store the temporary torrent
3657
+ {
3658
+ std::lock_guard<std::mutex> lock(metadata_mutex_);
3659
+ metadata_only_torrents_[info_hash] = metadata_torrent;
3660
+ }
3661
+
3662
+ // Start the metadata torrent (just for peer connections)
3663
+ if (!metadata_torrent->start()) {
3664
+ LOG_BT_ERROR("Failed to start metadata retrieval torrent");
3665
+
3666
+ // Cleanup and call callback with error
3667
+ {
3668
+ std::lock_guard<std::mutex> lock(metadata_mutex_);
3669
+ metadata_only_torrents_.erase(info_hash);
3670
+ auto it = metadata_retrieval_callbacks_.find(info_hash);
3671
+ if (it != metadata_retrieval_callbacks_.end()) {
3672
+ if (it->second) {
3673
+ it->second(TorrentInfo(), false, "Failed to start metadata retrieval");
3674
+ }
3675
+ metadata_retrieval_callbacks_.erase(it);
3676
+ }
3677
+ }
3678
+ return;
3679
+ }
3680
+
3681
+ // Directly connect to the specified peer - no DHT search needed!
3682
+ Peer peer{peer_ip, peer_port};
3683
+ LOG_BT_INFO("Connecting directly to peer " << peer_ip << ":" << peer_port << " for metadata exchange");
3684
+
3685
+ if (metadata_torrent->add_peer(peer)) {
3686
+ LOG_BT_INFO("Successfully initiated connection to peer " << peer_ip << ":" << peer_port);
3687
+ } else {
3688
+ LOG_BT_WARN("Failed to add peer " << peer_ip << ":" << peer_port << " - metadata retrieval may fail");
3689
+ // Don't fail immediately - the peer connection might still work
3690
+ }
3691
+
3692
+ LOG_BT_INFO("Metadata retrieval (direct) initiated for hash " << info_hash_to_hex(info_hash));
3693
+ }
3694
+
3695
+ void BitTorrentClient::get_torrent_metadata_from_peer(const std::string& info_hash_hex,
3696
+ const std::string& peer_ip,
3697
+ uint16_t peer_port,
3698
+ MetadataRetrievalCallback callback) {
3699
+ InfoHash info_hash = hex_to_info_hash(info_hash_hex);
3700
+
3701
+ // Validate the parsed hash
3702
+ bool is_zero = true;
3703
+ for (const auto& byte : info_hash) {
3704
+ if (byte != 0) {
3705
+ is_zero = false;
3706
+ break;
3707
+ }
3708
+ }
3709
+
3710
+ if (is_zero && info_hash_hex.length() == 40) {
3711
+ LOG_BT_ERROR("Invalid info hash format: " << info_hash_hex);
3712
+ if (callback) {
3713
+ callback(TorrentInfo(), false, "Invalid info hash format");
3714
+ }
3715
+ return;
3716
+ }
3717
+
3718
+ get_torrent_metadata_from_peer(info_hash, peer_ip, peer_port, callback);
3719
+ }
3720
+
3569
3721
  //=============================================================================
3570
3722
  // Utility Functions Implementation
3571
3723
  //=============================================================================
@@ -679,6 +679,31 @@ public:
679
679
  void get_torrent_metadata_by_hash(const InfoHash& info_hash, MetadataRetrievalCallback callback);
680
680
  void get_torrent_metadata_by_hash(const std::string& info_hash_hex, MetadataRetrievalCallback callback);
681
681
 
682
+ /**
683
+ * Get torrent metadata directly from a specific peer (fast path - no DHT search needed)
684
+ * This is more efficient when you already know a peer that has the torrent (e.g., from announce_peer)
685
+ * @param info_hash Info hash of the torrent
686
+ * @param peer_ip IP address of the peer
687
+ * @param peer_port Port of the peer
688
+ * @param callback Function called when metadata is retrieved (torrent_info, success, error_message)
689
+ */
690
+ void get_torrent_metadata_from_peer(const InfoHash& info_hash,
691
+ const std::string& peer_ip,
692
+ uint16_t peer_port,
693
+ MetadataRetrievalCallback callback);
694
+
695
+ /**
696
+ * Get torrent metadata directly from a specific peer by hex string (fast path)
697
+ * @param info_hash_hex Info hash as 40-character hex string
698
+ * @param peer_ip IP address of the peer
699
+ * @param peer_port Port of the peer
700
+ * @param callback Function called when metadata is retrieved (torrent_info, success, error_message)
701
+ */
702
+ void get_torrent_metadata_from_peer(const std::string& info_hash_hex,
703
+ const std::string& peer_ip,
704
+ uint16_t peer_port,
705
+ MetadataRetrievalCallback callback);
706
+
682
707
  private:
683
708
  std::atomic<bool> running_;
684
709
  int listen_port_;