librats 0.7.1 → 0.7.2

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.
@@ -541,7 +541,7 @@ private:
541
541
  void send_krpc_ping(const Peer& peer);
542
542
  void send_krpc_find_node(const Peer& peer, const NodeId& target);
543
543
  void send_krpc_get_peers(const Peer& peer, const InfoHash& info_hash);
544
- void send_krpc_announce_peer(const Peer& peer, const InfoHash& info_hash, uint16_t port, const std::string& token);
544
+ void send_krpc_announce_peer(const Peer& peer, const InfoHash& info_hash, uint16_t port, const std::string& token, bool implied_port = false);
545
545
 
546
546
  void add_node(const DhtNode& node, bool confirmed = true, bool no_verify = false);
547
547
  std::vector<DhtNode> find_closest_nodes(const NodeId& target, size_t count = K_BUCKET_SIZE);
@@ -281,7 +281,7 @@ bool IceManager::ensure_socket() {
281
281
  return false;
282
282
  }
283
283
 
284
- local_port_ = static_cast<uint16_t>(get_ephemeral_port(socket_));
284
+ local_port_ = static_cast<uint16_t>(get_bound_port(socket_));
285
285
  LOG_ICE_DEBUG("Created ICE socket on port " << local_port_);
286
286
  return true;
287
287
  }
@@ -60,7 +60,7 @@ KrpcMessage KrpcProtocol::create_get_peers_query(const std::string& transaction_
60
60
  return message;
61
61
  }
62
62
 
63
- KrpcMessage KrpcProtocol::create_announce_peer_query(const std::string& transaction_id, const NodeId& sender_id, const InfoHash& info_hash, uint16_t port, const std::string& token) {
63
+ KrpcMessage KrpcProtocol::create_announce_peer_query(const std::string& transaction_id, const NodeId& sender_id, const InfoHash& info_hash, uint16_t port, const std::string& token, bool implied_port) {
64
64
  KrpcMessage message;
65
65
  message.type = KrpcMessageType::Query;
66
66
  message.transaction_id = transaction_id;
@@ -68,6 +68,7 @@ KrpcMessage KrpcProtocol::create_announce_peer_query(const std::string& transact
68
68
  message.sender_id = sender_id;
69
69
  message.info_hash = info_hash;
70
70
  message.port = port;
71
+ message.implied_port = implied_port;
71
72
  message.token = token;
72
73
  return message;
73
74
  }
@@ -176,6 +177,9 @@ BencodeValue KrpcProtocol::encode_query(const KrpcMessage& message) {
176
177
  args["info_hash"] = BencodeValue(node_id_to_string(message.info_hash));
177
178
  args["port"] = BencodeValue(static_cast<int64_t>(message.port));
178
179
  args["token"] = BencodeValue(message.token);
180
+ if (message.implied_port) {
181
+ args["implied_port"] = BencodeValue(static_cast<int64_t>(1));
182
+ }
179
183
  break;
180
184
  }
181
185
 
@@ -316,6 +320,9 @@ std::unique_ptr<KrpcMessage> KrpcProtocol::decode_query(const BencodeValue& data
316
320
  if (args.has_key("token")) {
317
321
  message->token = args["token"].as_string();
318
322
  }
323
+ if (args.has_key("implied_port")) {
324
+ message->implied_port = (args["implied_port"].as_integer() != 0);
325
+ }
319
326
  break;
320
327
  }
321
328
 
@@ -68,6 +68,7 @@ struct KrpcMessage {
68
68
  NodeId target_id;
69
69
  InfoHash info_hash;
70
70
  uint16_t port;
71
+ bool implied_port; // BEP 5: if true, use UDP source port instead of 'port' field
71
72
  std::string token;
72
73
 
73
74
  // For responses
@@ -79,7 +80,7 @@ struct KrpcMessage {
79
80
  KrpcErrorCode error_code;
80
81
  std::string error_message;
81
82
 
82
- KrpcMessage() : type(KrpcMessageType::Query), query_type(KrpcQueryType::Ping), sender_id(), target_id(), info_hash(), port(0), response_id(), error_code(KrpcErrorCode::GenericError) {}
83
+ 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) {}
83
84
  };
84
85
 
85
86
  /**
@@ -96,7 +97,7 @@ public:
96
97
  static KrpcMessage create_ping_query(const std::string& transaction_id, const NodeId& sender_id);
97
98
  static KrpcMessage create_find_node_query(const std::string& transaction_id, const NodeId& sender_id, const NodeId& target_id);
98
99
  static KrpcMessage create_get_peers_query(const std::string& transaction_id, const NodeId& sender_id, const InfoHash& info_hash);
99
- static KrpcMessage create_announce_peer_query(const std::string& transaction_id, const NodeId& sender_id, const InfoHash& info_hash, uint16_t port, const std::string& token);
100
+ static KrpcMessage create_announce_peer_query(const std::string& transaction_id, const NodeId& sender_id, const InfoHash& info_hash, uint16_t port, const std::string& token, bool implied_port = false);
100
101
 
101
102
  static KrpcMessage create_ping_response(const std::string& transaction_id, const NodeId& response_id);
102
103
  static KrpcMessage create_find_node_response(const std::string& transaction_id, const NodeId& response_id, const std::vector<KrpcNode>& nodes);
@@ -146,16 +146,27 @@ bool RatsClient::start() {
146
146
  initialize_local_addresses();
147
147
 
148
148
  // Create dual-stack server socket (supports both IPv4 and IPv6)
149
- server_socket_ = create_tcp_server(listen_port_, 5, bind_address_);
149
+ server_socket_ = create_tcp_server(listen_port_, 5, bind_address_);
150
+ // Fallback to free port
151
+ if (!is_valid_socket(server_socket_) && listen_port_ > 0) {
152
+ // Requested port is not available, try ephemeral port as fallback
153
+ int original_port = listen_port_;
154
+ LOG_CLIENT_WARN("TCP port " << original_port << " is not available, falling back to ephemeral port");
155
+ server_socket_ = create_tcp_server(0, 5, bind_address_);
156
+ if (is_valid_socket(server_socket_)) {
157
+ listen_port_ = get_bound_port(server_socket_);
158
+ LOG_CLIENT_INFO("Fell back from port " << original_port << " to ephemeral port " << listen_port_);
159
+ }
160
+ }
150
161
  if (!is_valid_socket(server_socket_)) {
151
- LOG_CLIENT_ERROR("Failed to create dual-stack server socket on port " << listen_port_ <<
162
+ LOG_CLIENT_ERROR("Failed to create server socket on port " << listen_port_ <<
152
163
  (bind_address_.empty() ? "" : " bound to " + bind_address_));
153
164
  return false;
154
165
  }
155
166
 
156
167
  // Update listen_port_ with actual bound port if ephemeral port was requested
157
168
  if (listen_port_ == 0) {
158
- listen_port_ = get_ephemeral_port(server_socket_);
169
+ listen_port_ = get_bound_port(server_socket_);
159
170
  if (listen_port_ == 0) {
160
171
  LOG_CLIENT_WARN("Failed to get actual bound port - using port 0");
161
172
  } else {
@@ -448,7 +459,7 @@ void RatsClient::handle_client(socket_t client_socket, const std::string& peer_h
448
459
 
449
460
  // ----- 1. RECEIVE DATA -----
450
461
  LOG_CLIENT_DEBUG("Receiving data from socket " << client_socket);
451
- std::vector<uint8_t> received_bytes = receive_tcp_message_framed(client_socket);
462
+ std::vector<uint8_t> received_bytes = receive_tcp_message(client_socket);
452
463
 
453
464
  if (received_bytes.empty()) {
454
465
  break; // Connection closed or error
@@ -897,7 +908,7 @@ bool RatsClient::send_handshake_unlocked(socket_t socket, const std::string& our
897
908
  auto socket_mutex = get_socket_send_mutex(socket);
898
909
  std::lock_guard<std::mutex> send_lock(*socket_mutex);
899
910
 
900
- int sent = send_tcp_message_framed(socket, message_with_header);
911
+ int sent = send_tcp_message(socket, message_with_header);
901
912
  if (sent <= 0) {
902
913
  LOG_CLIENT_ERROR("Failed to send handshake to socket " << socket);
903
914
  return false;
@@ -1412,12 +1423,12 @@ bool RatsClient::send_binary_to_peer_unlocked(socket_t socket, const std::vector
1412
1423
  LOG_CLIENT_DEBUG("Sending encrypted message to " << peer_id_for_logging << " (" << ct_len << " bytes)");
1413
1424
 
1414
1425
  // Send encrypted message using framed protocol
1415
- int sent = send_tcp_message_framed(socket, ciphertext);
1426
+ int sent = send_tcp_message(socket, ciphertext);
1416
1427
  return sent > 0;
1417
1428
  }
1418
1429
 
1419
1430
  // Unencrypted path - use framed messages for reliable large message handling
1420
- int sent = send_tcp_message_framed(socket, message_with_header);
1431
+ int sent = send_tcp_message(socket, message_with_header);
1421
1432
  return sent > 0;
1422
1433
  }
1423
1434
 
@@ -1972,6 +1983,33 @@ bool RatsClient::is_automatic_discovery_running() const {
1972
1983
  return auto_discovery_running_.load();
1973
1984
  }
1974
1985
 
1986
+ std::chrono::seconds RatsClient::calculate_discovery_interval() const {
1987
+ int peer_count = get_peer_count();
1988
+
1989
+ // No peers - aggressive discovery
1990
+ if (peer_count == 0) {
1991
+ return std::chrono::seconds(15);
1992
+ }
1993
+
1994
+ // Calculate fill ratio
1995
+ float fill_ratio = static_cast<float>(peer_count) / static_cast<float>(max_peers_);
1996
+
1997
+ // Graduated intervals based on fill ratio
1998
+ if (fill_ratio < 0.25f) {
1999
+ // Less than 25% full - still fairly aggressive
2000
+ return std::chrono::seconds(60); // 1 minute
2001
+ } else if (fill_ratio < 0.50f) {
2002
+ // 25-50% full - moderate
2003
+ return std::chrono::seconds(180); // 3 minutes
2004
+ } else if (fill_ratio < 0.75f) {
2005
+ // 50-75% full - relaxed
2006
+ return std::chrono::seconds(600); // 10 minutes
2007
+ } else {
2008
+ // 75-100% full - very relaxed (mostly just re-announcing)
2009
+ return std::chrono::seconds(1800); // 30 minutes
2010
+ }
2011
+ }
2012
+
1975
2013
  void RatsClient::automatic_discovery_loop() {
1976
2014
  LOG_CLIENT_INFO("Automatic peer discovery loop started");
1977
2015
 
@@ -1993,12 +2031,12 @@ void RatsClient::automatic_discovery_loop() {
1993
2031
  auto now = std::chrono::steady_clock::now();
1994
2032
 
1995
2033
  // Announce combines both announcing our presence and discovering peers
1996
- // Adjust frequency based on whether we have peers
1997
- auto interval = (get_peer_count() == 0)
1998
- ? std::chrono::seconds(15) // Aggressive when no peers
1999
- : std::chrono::minutes(10); // Less aggressive when connected
2034
+ // Interval scales based on peer count: aggressive when empty, relaxed when nearly full
2035
+ auto interval = calculate_discovery_interval();
2000
2036
 
2001
2037
  if (now - last_announce >= interval) {
2038
+ LOG_CLIENT_DEBUG("Discovery interval: " << interval.count() << "s (peers: "
2039
+ << get_peer_count() << "/" << max_peers_ << ")");
2002
2040
  announce_rats_peer();
2003
2041
  last_announce = now;
2004
2042
  }
@@ -636,6 +636,13 @@ public:
636
636
  */
637
637
  bool is_automatic_discovery_running() const;
638
638
 
639
+ /**
640
+ * Calculate discovery interval based on current peer count
641
+ * Uses graduated scaling: more aggressive when fewer peers, less aggressive when nearly full
642
+ * @return Discovery interval in seconds
643
+ */
644
+ std::chrono::seconds calculate_discovery_interval() const;
645
+
639
646
  /**
640
647
  * Get the discovery hash for current protocol configuration
641
648
  * @return Discovery hash based on current protocol name and version
@@ -1084,6 +1091,20 @@ public:
1084
1091
  */
1085
1092
  void set_log_retention_count(int count);
1086
1093
 
1094
+ /**
1095
+ * Enable or disable log rotation on application startup
1096
+ * When enabled, the existing log file will be rotated when logging starts,
1097
+ * so each application run gets a fresh log file
1098
+ * @param enabled Whether to rotate logs on startup (default: false)
1099
+ */
1100
+ void set_log_rotate_on_startup(bool enabled);
1101
+
1102
+ /**
1103
+ * Check if log rotation on startup is enabled
1104
+ * @return true if rotate on startup is enabled
1105
+ */
1106
+ bool is_log_rotate_on_startup_enabled() const;
1107
+
1087
1108
  /**
1088
1109
  * Clear/reset the current log file
1089
1110
  */
@@ -248,12 +248,13 @@ void RatsClient::get_torrent_metadata_from_peer(const InfoHash& info_hash,
248
248
  return;
249
249
  }
250
250
 
251
- LOG_CLIENT_INFO("Fetching metadata from peer " << peer_ip << ":" << peer_port << " (fast path)");
251
+ LOG_CLIENT_INFO("Fetching metadata from peer " << peer_ip << ":" << peer_port << " (fast path, no DHT)");
252
252
 
253
253
  // Add magnet in metadata-only mode (empty save_path) and immediately add the peer
254
+ // skip_dht_search=true to avoid network-wide DHT queries - we only want this specific peer
254
255
  std::string hash_hex = info_hash_to_hex(info_hash);
255
256
  std::string magnet = "magnet:?xt=urn:btih:" + hash_hex;
256
- auto torrent = bittorrent_client_->add_magnet(magnet, "");
257
+ auto torrent = bittorrent_client_->add_magnet(magnet, "", true /* skip_dht_search */);
257
258
 
258
259
  if (!torrent) {
259
260
  if (callback) {
@@ -237,6 +237,18 @@ void rats_set_log_retention_count(rats_client_t handle, int count) {
237
237
  wrap->client->set_log_retention_count(count);
238
238
  }
239
239
 
240
+ void rats_set_log_rotate_on_startup(rats_client_t handle, int enabled) {
241
+ if (!handle) return;
242
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
243
+ wrap->client->set_log_rotate_on_startup(enabled != 0);
244
+ }
245
+
246
+ int rats_is_log_rotate_on_startup_enabled(rats_client_t handle) {
247
+ if (!handle) return 0;
248
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
249
+ return wrap->client->is_log_rotate_on_startup_enabled() ? 1 : 0;
250
+ }
251
+
240
252
  void rats_clear_log_file(rats_client_t handle) {
241
253
  if (!handle) return;
242
254
  rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
@@ -53,6 +53,8 @@ RATS_API void rats_set_log_timestamps_enabled(rats_client_t client, int enabled)
53
53
  RATS_API int rats_is_log_timestamps_enabled(rats_client_t client);
54
54
  RATS_API void rats_set_log_rotation_size(rats_client_t client, size_t max_size_bytes);
55
55
  RATS_API void rats_set_log_retention_count(rats_client_t client, int count);
56
+ RATS_API void rats_set_log_rotate_on_startup(rats_client_t client, int enabled);
57
+ RATS_API int rats_is_log_rotate_on_startup_enabled(rats_client_t client);
56
58
  RATS_API void rats_clear_log_file(rats_client_t client);
57
59
 
58
60
  // Error codes
@@ -136,6 +136,15 @@ void RatsClient::set_log_retention_count(int count) {
136
136
  LOG_CLIENT_INFO("Log retention count set to: " << count << " files");
137
137
  }
138
138
 
139
+ void RatsClient::set_log_rotate_on_startup(bool enabled) {
140
+ Logger::getInstance().set_rotate_on_startup(enabled);
141
+ LOG_CLIENT_INFO("Log rotation on startup " << (enabled ? "enabled" : "disabled"));
142
+ }
143
+
144
+ bool RatsClient::is_log_rotate_on_startup_enabled() const {
145
+ return Logger::getInstance().is_rotate_on_startup_enabled();
146
+ }
147
+
139
148
  void RatsClient::clear_log_file() {
140
149
  Logger& logger = Logger::getInstance();
141
150
  std::string log_path = logger.get_log_file_path();
@@ -100,6 +100,17 @@ public:
100
100
  max_log_files_ = count;
101
101
  }
102
102
 
103
+ // Rotate on startup configuration
104
+ void set_rotate_on_startup(bool enabled) {
105
+ std::lock_guard<std::mutex> lock(mutex_);
106
+ rotate_on_startup_ = enabled;
107
+ }
108
+
109
+ bool is_rotate_on_startup_enabled() const {
110
+ std::lock_guard<std::mutex> lock(mutex_);
111
+ return rotate_on_startup_;
112
+ }
113
+
103
114
  // Get current file logging status
104
115
  bool is_file_logging_enabled() const {
105
116
  std::lock_guard<std::mutex> lock(mutex_);
@@ -178,7 +189,8 @@ public:
178
189
  private:
179
190
  Logger() : min_level_(LogLevel::INFO), colors_enabled_(true), timestamps_enabled_(true),
180
191
  console_logging_enabled_(true), file_logging_enabled_(false),
181
- max_log_file_size_(10 * 1024 * 1024), max_log_files_(5), current_file_size_(0) {
192
+ max_log_file_size_(10 * 1024 * 1024), max_log_files_(5), current_file_size_(0),
193
+ rotate_on_startup_(false), startup_rotation_done_(false) {
182
194
  // Check if we're outputting to a terminal
183
195
  is_terminal_ = isatty(fileno(stdout));
184
196
 
@@ -330,6 +342,19 @@ private:
330
342
  }
331
343
  }
332
344
 
345
+ // Perform rotation on startup if enabled and not yet done this session
346
+ if (rotate_on_startup_ && !startup_rotation_done_) {
347
+ // Check if the log file exists and has content
348
+ if (file_exists(log_file_path_.c_str())) {
349
+ std::ifstream check_file(log_file_path_, std::ios::ate);
350
+ if (check_file.is_open() && check_file.tellg() > 0) {
351
+ check_file.close();
352
+ rotate_log_files_impl();
353
+ }
354
+ }
355
+ startup_rotation_done_ = true;
356
+ }
357
+
333
358
  log_file_.open(log_file_path_, std::ios::app);
334
359
  if (log_file_.is_open()) {
335
360
  // Get current file size
@@ -350,6 +375,16 @@ private:
350
375
  if (log_file_path_.empty() || max_log_files_ <= 0) return;
351
376
 
352
377
  close_log_file();
378
+ rotate_log_files_impl();
379
+
380
+ // Reopen the log file (new empty file)
381
+ open_log_file();
382
+ }
383
+
384
+ // Internal rotation implementation - does not close/open files
385
+ // Called directly when we need to rotate before opening (e.g., on startup)
386
+ void rotate_log_files_impl() {
387
+ if (log_file_path_.empty() || max_log_files_ <= 0) return;
353
388
 
354
389
  // Move existing log files
355
390
  for (int i = max_log_files_ - 1; i >= 1; i--) {
@@ -368,9 +403,6 @@ private:
368
403
  // Move current log file to .1
369
404
  std::string backup_name = log_file_path_ + ".1";
370
405
  std::rename(log_file_path_.c_str(), backup_name.c_str());
371
-
372
- // Reopen the log file (new empty file)
373
- open_log_file();
374
406
  }
375
407
 
376
408
  mutable std::mutex mutex_;
@@ -389,6 +421,10 @@ private:
389
421
  size_t max_log_file_size_;
390
422
  int max_log_files_;
391
423
  size_t current_file_size_;
424
+
425
+ // Rotate on startup members
426
+ bool rotate_on_startup_;
427
+ bool startup_rotation_done_; // Tracks if we've already done startup rotation for this session
392
428
  };
393
429
 
394
430
  } // namespace librats