Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package libtorrent for openSUSE:Factory 
checked in at 2026-08-27 18:51:47
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/libtorrent (Old)
 and      /work/SRC/openSUSE:Factory/.libtorrent.new.1265 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "libtorrent"

Thu Aug 27 18:51:47 2026 rev:41 rq:1373889 version:0.16.21

Changes:
--------
--- /work/SRC/openSUSE:Factory/libtorrent/libtorrent.changes    2026-08-09 
21:36:52.965395270 +0200
+++ /work/SRC/openSUSE:Factory/.libtorrent.new.1265/libtorrent.changes  
2026-08-27 18:55:16.368742303 +0200
@@ -1,0 +2,8 @@
+Wed Aug 26 19:28:47 UTC 2026 - Jan Engelhardt <[email protected]>
+
+- Update to release 0.16.21
+  * This release includes various bugfixes and minor improvements.
+  * Check for empty paths, zero-length datagrams,
+    file lengths. Restrict lengths of DHT tokens.
+
+-------------------------------------------------------------------

Old:
----
  libtorrent-0.16.20.tar.gz

New:
----
  libtorrent-0.16.21.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ libtorrent.spec ++++++
--- /var/tmp/diff_new_pack.sOeJxG/_old  2026-08-27 18:55:17.100767860 +0200
+++ /var/tmp/diff_new_pack.sOeJxG/_new  2026-08-27 18:55:17.102767930 +0200
@@ -18,7 +18,7 @@
 
 %define lname  libtorrent49
 Name:           libtorrent
-Version:        0.16.20
+Version:        0.16.21
 Release:        0
 Summary:        A BitTorrent library written in C++
 License:        SUSE-GPL-2.0+-with-openssl-exception

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.sOeJxG/_old  2026-08-27 18:55:17.133769012 +0200
+++ /var/tmp/diff_new_pack.sOeJxG/_new  2026-08-27 18:55:17.136769117 +0200
@@ -1,5 +1,5 @@
-mtime: 1786032516
-commit: 9b4007009a13e6473225e993783a5543fd8169bfacb11a9c42acefe12867b5a8
+mtime: 1787772701
+commit: ea673a9befb1a9b3bcdfbab83956c209cb936f34e8b830e90f7cd564ecc8c5db
 url: https://src.opensuse.org/jengelh/libtorrent
 revision: master
 

++++++ build.specials.obscpio ++++++

++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore      1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore      2026-08-26 21:31:41.000000000 +0200
@@ -0,0 +1 @@
+.osc

++++++ libtorrent-0.16.20.tar.gz -> libtorrent-0.16.21.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/configure 
new/libtorrent-0.16.21/configure
--- old/libtorrent-0.16.20/configure    2026-08-06 10:14:25.000000000 +0200
+++ new/libtorrent-0.16.21/configure    2026-08-26 09:17:56.000000000 +0200
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.72 for libtorrent 0.16.20.
+# Generated by GNU Autoconf 2.72 for libtorrent 0.16.21.
 #
 # Report bugs to <[email protected]>.
 #
@@ -614,8 +614,8 @@
 # Identity of this package.
 PACKAGE_NAME='libtorrent'
 PACKAGE_TARNAME='libtorrent'
-PACKAGE_VERSION='0.16.20'
-PACKAGE_STRING='libtorrent 0.16.20'
+PACKAGE_VERSION='0.16.21'
+PACKAGE_STRING='libtorrent 0.16.21'
 PACKAGE_BUGREPORT='[email protected]'
 PACKAGE_URL=''
 
@@ -1408,7 +1408,7 @@
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-'configure' configures libtorrent 0.16.20 to adapt to many kinds of systems.
+'configure' configures libtorrent 0.16.21 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1479,7 +1479,7 @@
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of libtorrent 0.16.20:";;
+     short | recursive ) echo "Configuration of libtorrent 0.16.21:";;
    esac
   cat <<\_ACEOF
 
@@ -1634,7 +1634,7 @@
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-libtorrent configure 0.16.20
+libtorrent configure 0.16.21
 generated by GNU Autoconf 2.72
 
 Copyright (C) 2023 Free Software Foundation, Inc.
@@ -2352,7 +2352,7 @@
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by libtorrent $as_me 0.16.20, which was
+It was created by libtorrent $as_me 0.16.21, which was
 generated by GNU Autoconf 2.72.  Invocation command line was
 
   $ $0$ac_configure_args_raw
@@ -4045,7 +4045,7 @@
 
 # Define the identity of the package.
  PACKAGE='libtorrent'
- VERSION='0.16.20'
+ VERSION='0.16.21'
 
 
 printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
@@ -23348,7 +23348,7 @@
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by libtorrent $as_me 0.16.20, which was
+This file was extended by libtorrent $as_me 0.16.21, which was
 generated by GNU Autoconf 2.72.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -23416,7 +23416,7 @@
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config='$ac_cs_config_escaped'
 ac_cs_version="\\
-libtorrent config.status 0.16.20
+libtorrent config.status 0.16.21
 configured by $0, generated by GNU Autoconf 2.72,
   with options \\"\$ac_cs_config\\"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/configure.ac 
new/libtorrent-0.16.21/configure.ac
--- old/libtorrent-0.16.20/configure.ac 2026-08-06 10:14:07.000000000 +0200
+++ new/libtorrent-0.16.21/configure.ac 2026-08-26 09:17:41.000000000 +0200
@@ -1,4 +1,4 @@
-AC_INIT([[libtorrent]],[[0.16.20]],[[[email protected]]])
+AC_INIT([[libtorrent]],[[0.16.21]],[[[email protected]]])
 
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_MACRO_DIRS([scripts])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/data/chunk.cc 
new/libtorrent-0.16.21/src/data/chunk.cc
--- old/libtorrent-0.16.20/src/data/chunk.cc    2026-08-06 10:14:07.000000000 
+0200
+++ new/libtorrent-0.16.21/src/data/chunk.cc    2026-08-26 09:17:41.000000000 
+0200
@@ -48,13 +48,34 @@
 #include "chunk_iterator.h"
 
 namespace {
-jmp_buf jmp_disk_full;
+thread_local jmp_buf jmp_disk_full;
 
 void
 bus_handler(int, siginfo_t* si, void*) {
   if (si && si->si_code == BUS_ADRERR)
     longjmp(jmp_disk_full, 1);
 }
+
+class bus_handler_guard {
+public:
+  bus_handler_guard() {
+    struct sigaction sa{};
+
+    sa.sa_sigaction = &bus_handler;
+    sa.sa_flags = SA_SIGINFO;
+    sigfillset(&sa.sa_mask);
+
+    sigaction(SIGBUS, &sa, &m_oldact);
+  }
+
+  ~bus_handler_guard() { sigaction(SIGBUS, &m_oldact, nullptr); }
+
+  bus_handler_guard(const bus_handler_guard&) = delete;
+  bus_handler_guard& operator=(const bus_handler_guard&) = delete;
+
+private:
+  struct sigaction m_oldact;
+};
 } // namespace
 
 namespace torrent {
@@ -236,12 +257,6 @@
 // matching.
 bool
 Chunk::from_buffer(const void* buffer, uint32_t position, uint32_t length) {
-  struct sigaction sa{}, oldact;
-  sa.sa_sigaction = &bus_handler;
-  sa.sa_flags = SA_SIGINFO;
-  sigfillset(&sa.sa_mask);
-  sigaction(SIGBUS, &sa, &oldact);
-
   if (position + length > m_chunkSize)
     throw internal_error("Chunk::from_buffer(...) position + length > 
m_chunkSize.");
 
@@ -251,6 +266,8 @@
   Chunk::data_type data;
   ChunkIterator itr(this, position, position + length);
 
+  bus_handler_guard guard;
+
   if (setjmp(jmp_disk_full) == 0) {
       do {
         data = itr.data();
@@ -262,8 +279,6 @@
       throw storage_error("no space left on disk");
   }
 
-  sigaction(SIGBUS, &oldact, NULL);
-  
   return true;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/dht/dht_hash_map.h 
new/libtorrent-0.16.21/src/dht/dht_hash_map.h
--- old/libtorrent-0.16.20/src/dht/dht_hash_map.h       2026-08-06 
10:14:07.000000000 +0200
+++ new/libtorrent-0.16.21/src/dht/dht_hash_map.h       2026-08-26 
09:17:41.000000000 +0200
@@ -39,6 +39,7 @@
 
 #include "config.h"
 
+#include <cstring>
 #include <unordered_map>
 
 #include "dht_node.h"
@@ -60,35 +61,21 @@
 
 struct hashstring_ptr_hash {
   size_t operator () (const HashString* n) const {
-#if USE_ALIGNED
-    size_t result = 0;
-    const char *first = n->data() + hashstring_hash_ofs;
-    const char *last = first + sizeof(size_t);
-
-    while (first != last)
-      result = (result << 8) + *first++;
-    
+    size_t result;
+
+    std::memcpy(&result, n->data() + hashstring_hash_ofs, sizeof(result));
+
     return result;
-#else
-    return *reinterpret_cast<const size_t*>(n->data() + hashstring_hash_ofs);
-#endif
   }
 };
 
 struct hashstring_hash {
   size_t operator () (const HashString& n) const {
-#if USE_ALIGNED
-    size_t result = 0;
-    const char *first = n.data() + hashstring_hash_ofs;
-    const char *last = first + sizeof(size_t);
-
-    while (first != last)
-      result = (result << 8) + *first++;
-    
+    size_t result;
+
+    std::memcpy(&result, n.data() + hashstring_hash_ofs, sizeof(result));
+
     return result;
-#else
-    return *reinterpret_cast<const size_t*>(n.data() + hashstring_hash_ofs);
-#endif
   }
 };
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/dht/dht_router.cc 
new/libtorrent-0.16.21/src/dht/dht_router.cc
--- old/libtorrent-0.16.20/src/dht/dht_router.cc        2026-08-06 
10:14:07.000000000 +0200
+++ new/libtorrent-0.16.21/src/dht/dht_router.cc        2026-08-26 
09:17:41.000000000 +0200
@@ -231,8 +231,10 @@
   if (sa_tmp->sa_family != AF_INET)
     return;
 
-  if (sap_is_any(sa_tmp))
-    throw input_error("DhtRouter::contact() called with any address.");
+  if (sap_is_any(sa_tmp)) {
+    LT_LOG_THIS("not contacting node, any address : %s", 
sa_addr_str(sa_tmp.get()).c_str());
+    return;
+  }
 
   sap_set_port(sa_tmp, port);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/dht/dht_server.cc 
new/libtorrent-0.16.21/src/dht/dht_server.cc
--- old/libtorrent-0.16.20/src/dht/dht_server.cc        2026-08-06 
10:14:07.000000000 +0200
+++ new/libtorrent-0.16.21/src/dht/dht_server.cc        2026-08-26 
09:17:41.000000000 +0200
@@ -490,6 +490,11 @@
   if (response[key_r_values].is_raw_list())
     announce->receive_peers(response[key_r_values].as_raw_list());
 
+  // Restrict the length of tokens. We echo them back in announce_peer, and the
+  // query has to fit in a single packet.
+  if (response[key_r_token].is_raw_string() && 
response[key_r_token].as_raw_string().size() > 64)
+    throw dht_error(dht_error_protocol, "Token length too long");
+
   if (response[key_r_token].is_raw_string())
     add_transaction(std::unique_ptr<DhtTransaction>(new 
DhtTransactionAnnouncePeer(transaction->id(),
                                                                                
    transaction->address(),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libtorrent-0.16.20/src/download/download_constructor.cc 
new/libtorrent-0.16.21/src/download/download_constructor.cc
--- old/libtorrent-0.16.20/src/download/download_constructor.cc 2026-08-06 
10:14:07.000000000 +0200
+++ new/libtorrent-0.16.21/src/download/download_constructor.cc 2026-08-26 
09:17:41.000000000 +0200
@@ -4,6 +4,7 @@
 
 #include <algorithm>
 #include <cstring>
+#include <limits>
 
 #include "manager.h"
 #include "download/download_wrapper.h"
@@ -37,12 +38,7 @@
   if (is_invalid_path_element(b.get_key("name")))
     throw input_error("Bad torrent file, \"name\" is an invalid path name.");
 
-  auto& name = b.get_key_string("name");
-
-  if (name.empty())
-    throw internal_error("DownloadConstructor::parse_name(...) Ended up with 
an empty Path.");
-
-  m_download->info()->set_name(name);
+  m_download->info()->set_name(b.get_key_string("name"));
 }
 
 void
@@ -61,6 +57,9 @@
     m_download->info()->set_flags(DownloadInfo::flag_meta_download);
 
   if (m_download->info()->is_meta_download()) {
+    if (b.has_key("length") || b.has_key("files"))
+      throw input_error("Meta-download has file entries.");
+
     if (b.get_key_string("pieces").length() != HashString::size_data)
       throw input_error("Meta-download has invalid piece data.");
 
@@ -68,10 +67,12 @@
     parse_single_file(b, chunkSize);
 
   } else {
-    chunkSize = b.get_key_value("piece length");
+    int64_t piece_length = b.get_key_value("piece length");
 
-    if (chunkSize <= (1 << 10) || chunkSize > (512 << 20))
+    if (piece_length <= (1 << 10) || piece_length > (512 << 20))
       throw input_error("Torrent has an invalid \"piece length\".");
+
+    chunkSize = piece_length;
   }
 
   if (b.has_key("length")) {
@@ -151,6 +152,7 @@
 DownloadConstructor::is_valid_path_element(const Object& b) {
   return
     b.is_string() &&
+    !b.as_string().empty() &&
     b.as_string() != "." &&
     b.as_string() != ".." &&
     std::find(b.as_string().begin(), b.as_string().end(), '/') == 
b.as_string().end() &&
@@ -162,8 +164,13 @@
   if (is_invalid_path_element(b.get_key("name")))
     throw input_error("Bad torrent file, \"name\" is an invalid path name.");
 
+  int64_t length = chunkSize == 1 ? 1 : b.get_key_value("length");
+
+  if (length < 0)
+    throw input_error("Bad torrent file, invalid length for file.");
+
   FileList* fileList = m_download->main()->file_list();
-  fileList->initialize(chunkSize == 1 ? 1 : b.get_key_value("length"), 
chunkSize);
+  fileList->initialize(length, chunkSize);
   fileList->set_multi_file(false);
 
   Path path;
@@ -199,7 +206,7 @@
 
     int64_t length = object.get_key_value("length");
 
-    if (length < 0 || torrent_size + length < 0)
+    if (length < 0 || length > std::numeric_limits<int64_t>::max() - 
torrent_size)
       throw input_error("Bad torrent file, invalid length for file.");
 
     torrent_size += length;
@@ -214,6 +221,17 @@
     split_list.emplace_back(length, path, attr_flags);
   }
 
+  std::vector<const Path*> sorted_paths;
+  sorted_paths.reserve(split_list.size());
+
+  for (const auto& split : split_list)
+    sorted_paths.push_back(&std::get<1>(split));
+
+  std::sort(sorted_paths.begin(), sorted_paths.end(), &Path::compare_less);
+
+  if (std::adjacent_find(sorted_paths.begin(), sorted_paths.end(), 
&Path::is_prefix) != sorted_paths.end())
+    throw input_error("Bad torrent file, a file path is a duplicate or the 
prefix of another.");
+
   FileList* file_list = m_download->main()->file_list();
   file_list->set_multi_file(true);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/net/curl_stack.cc 
new/libtorrent-0.16.21/src/net/curl_stack.cc
--- old/libtorrent-0.16.20/src/net/curl_stack.cc        2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/net/curl_stack.cc        2026-08-26 
09:17:41.000000000 +0200
@@ -1,5 +1,7 @@
 #include "config.h"
 
+#include "torrent/runtime/socket_manager.h"
+
 #include "curl_stack.h"
 
 #include <algorithm>
@@ -54,7 +56,7 @@
 void
 CurlStack::set_max_cache_connections(unsigned int value) {
   if (value > 1024)
-    throw torrent::internal_error("CurlStack::set_max_cache_connections() 
called with a value greater than 1024.");
+    throw torrent::input_error("CurlStack::set_max_cache_connections() called 
with a value greater than 1024.");
 
   auto guard = lock_guard();
 
@@ -66,7 +68,7 @@
 void
 CurlStack::set_max_host_connections(unsigned int value) {
   if (value > 1024)
-    throw torrent::internal_error("CurlStack::set_max_host_connections() 
called with a value greater than 1024.");
+    throw torrent::input_error("CurlStack::set_max_host_connections() called 
with a value greater than 1024.");
 
   auto guard = lock_guard();
 
@@ -77,8 +79,9 @@
 
 void
 CurlStack::set_max_total_connections(unsigned int value) {
-  if (value > 4096)
-    throw torrent::internal_error("CurlStack::set_max_total_connections() 
called with a value greater than 4096.");
+  if (value > torrent::runtime::SocketManager::http_max_alloc)
+    throw torrent::internal_error("CurlStack::set_max_total_connections() 
called with a value greater than " +
+                                  
std::to_string(torrent::runtime::SocketManager::http_max_alloc) + ".");
 
   auto guard = lock_guard();
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/net/udns_resolver.cc 
new/libtorrent-0.16.21/src/net/udns_resolver.cc
--- old/libtorrent-0.16.20/src/net/udns_resolver.cc     2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/net/udns_resolver.cc     2026-08-26 
09:17:41.000000000 +0200
@@ -160,7 +160,7 @@
 
       // Unrecoverable errors, like ENOMEM.
       if (::dns_status(m_ctx) != DNS_E_BADQUERY)
-        throw new internal_error("dns_submit_a4 failed");
+        throw internal_error("dns_submit_a4 failed");
 
       // UDNS will fail immediately during submission of malformed domain 
names,
       // e.g., `..`. In order to maintain a clean interface, keep track of this
@@ -192,7 +192,7 @@
       }
 
       if (::dns_status(m_ctx) != DNS_E_BADQUERY)
-        throw new internal_error("dns_submit_a6 failed");
+        throw internal_error("dns_submit_a6 failed");
 
       query->error_sin = EAI_NONAME;
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/protocol/extensions.cc 
new/libtorrent-0.16.21/src/protocol/extensions.cc
--- old/libtorrent-0.16.20/src/protocol/extensions.cc   2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/protocol/extensions.cc   2026-08-26 
09:17:41.000000000 +0200
@@ -172,11 +172,17 @@
   auto end = buffer;
 
   end += sprintf(end, "d5:added%d:", added_len);
-  memcpy(end, added.begin()->c_str(), added_len);
+
+  if (!added.empty())
+    memcpy(end, added.begin()->c_str(), added_len);
+
   end += added_len;
 
   end += sprintf(end, "7:dropped%d:", removed_len);
-  memcpy(end, removed.begin()->c_str(), removed_len);
+
+  if (!removed.empty())
+    memcpy(end, removed.begin()->c_str(), removed_len);
+
   end += removed_len;
 
   *end++ = 'e';
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/protocol/handshake.cc 
new/libtorrent-0.16.21/src/protocol/handshake.cc
--- old/libtorrent-0.16.20/src/protocol/handshake.cc    2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/protocol/handshake.cc    2026-08-26 
09:17:41.000000000 +0200
@@ -467,7 +467,7 @@
 
       m_encryption.info()->decrypt(m_readBuffer.position(), 
std::min<uint32_t>(m_readPos, m_readBuffer.remaining()));
 
-    } if (m_encryption.is_stream_encrypted()) {
+    } else if (m_encryption.is_stream_encrypted()) {
       LT_LOG_EXTRA_DEBUG_SA(m_address, "read_encryption_negotiation: peer 
offered encrypted stream", 0);
 
       if (m_encryption.policy().require_plaintext_stream())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libtorrent-0.16.20/src/protocol/peer_connection_base.cc 
new/libtorrent-0.16.21/src/protocol/peer_connection_base.cc
--- old/libtorrent-0.16.20/src/protocol/peer_connection_base.cc 2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/protocol/peer_connection_base.cc 2026-08-26 
09:17:41.000000000 +0200
@@ -546,7 +546,8 @@
     return false;
   }
 
-  uint32_t length = read_stream_throws(m_nullBuffer, std::min(quota, 
m_request_list.transfer()->piece().length() - 
m_request_list.transfer()->position()));
+  uint32_t remaining = m_request_list.transfer()->piece().length() - 
m_request_list.transfer()->position();
+  uint32_t length = read_stream_throws(m_nullBuffer, std::min({quota, 
remaining, static_cast<uint32_t>(null_buffer_size)}));
   throttle->node_used(m_peer_chunks.download_throttle(), length);
 
   if (is_encrypted())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/data/file_list.cc 
new/libtorrent-0.16.21/src/torrent/data/file_list.cc
--- old/libtorrent-0.16.20/src/torrent/data/file_list.cc        2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/torrent/data/file_list.cc        2026-08-26 
09:17:41.000000000 +0200
@@ -411,6 +411,9 @@
 
       // Update the path during open so that any changes to root dir
       // and file paths are properly handled.
+      if (entry->path()->empty())
+        throw storage_error("Empty filename is not allowed.");
+
       if (entry->path()->back().empty())
         entry->set_frozen_path(std::string());
       else
@@ -422,8 +425,6 @@
       if (entry->size_bytes() > m_max_file_size)
         throw storage_error("File exceedes the configured max file size.");
 
-      if (entry->path()->empty())
-        throw storage_error("Empty filename is not allowed.");
 
       // Handle directory creation outside of open_file, so we can do
       // it here if necessary.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libtorrent-0.16.20/src/torrent/download/choke_queue.cc 
new/libtorrent-0.16.21/src/torrent/download/choke_queue.cc
--- old/libtorrent-0.16.20/src/torrent/download/choke_queue.cc  2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/torrent/download/choke_queue.cc  2026-08-26 
09:17:41.000000000 +0200
@@ -156,6 +156,11 @@
 
 void
 choke_queue::balance() {
+  // A group that was never given its slots cannot be balanced; calling through
+  // an empty std::function would terminate the client.
+  if (!m_slotCanUnchoke)
+    return;
+
   LT_LOG_THIS("balancing queue: heuristics:%i currently_unchoked:%" PRIu32 " 
max_unchoked:%" PRIu32,
               m_heuristics,
               m_currently_unchoked,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libtorrent-0.16.20/src/torrent/download/resource_manager.cc 
new/libtorrent-0.16.21/src/torrent/download/resource_manager.cc
--- old/libtorrent-0.16.20/src/torrent/download/resource_manager.cc     
2026-08-06 10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/torrent/download/resource_manager.cc     
2026-08-26 09:17:41.000000000 +0200
@@ -3,6 +3,7 @@
 #include <algorithm>
 #include <functional>
 #include <limits>
+#include <memory>
 #include <numeric>
 
 #include "download/download_main.h"
@@ -62,13 +63,13 @@
   auto group_itr = m_choke_groups.begin();
 
   while (group_itr != m_choke_groups.end()) {
-    (*group_itr)->set_first(&*entry_itr);
+    (*group_itr)->set_first(std::to_address(entry_itr));
 
     entry_itr = std::find_if(entry_itr, end(), [group_itr, this](value_type v) 
{
       return (std::distance(m_choke_groups.begin(), group_itr)) < v.group();
     });
 
-    (*group_itr)->set_last(&*entry_itr);
+    (*group_itr)->set_last(std::to_address(entry_itr));
     group_itr++;
   }
 }
@@ -79,14 +80,14 @@
   auto group_itr = m_choke_groups.begin();
 
   while (group_itr != m_choke_groups.end()) {
-    if ((*group_itr)->first() != &*entry_itr)
+    if ((*group_itr)->first() != std::to_address(entry_itr))
       throw internal_error("ResourceManager::receive_tick() invalid first 
iterator.");
 
     entry_itr = std::find_if(entry_itr, end(), [group_itr, this](value_type v) 
{
       return (std::distance(m_choke_groups.begin(), group_itr)) < v.group();
     });
 
-    if ((*group_itr)->last() != &*entry_itr)
+    if ((*group_itr)->last() != std::to_address(entry_itr))
       throw internal_error("ResourceManager::receive_tick() invalid last 
iterator.");
 
     group_itr++;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/download.cc 
new/libtorrent-0.16.21/src/torrent/download.cc
--- old/libtorrent-0.16.20/src/torrent/download.cc      2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/torrent/download.cc      2026-08-26 
09:17:41.000000000 +0200
@@ -339,6 +339,9 @@
   if (m_ptr->main()->file_list()->bitfield()->empty())
     throw input_error("Download::clear_range(...) Bitfield is empty.");
 
+  if (first > last || last > 
m_ptr->main()->file_list()->bitfield()->size_bits())
+    throw input_error("Download::update_range(...) Range is out of bounds.");
+
   if (flags & update_range_recheck)
     m_ptr->hash_checker()->hashing_ranges().insert(first, last);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/object_stream.cc 
new/libtorrent-0.16.21/src/torrent/object_stream.cc
--- old/libtorrent-0.16.20/src/torrent/object_stream.cc 2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/torrent/object_stream.cc 2026-08-26 
09:17:41.000000000 +0200
@@ -1,7 +1,10 @@
 #include "config.h"
 
+#include <algorithm>
+
 #include "torrent/object_stream.h"
 
+#include <charconv>
 #include <iostream>
 #include <iterator>
 #include <limits>
@@ -16,23 +19,31 @@
 
 static bool
 object_read_string(std::istream* input, std::string& str) {
-  uint32_t size;
-  *input >> size;
+  uint32_t remaining;
+  *input >> remaining;
 
   if (input->fail() || input->get() != ':')
     return false;
 
-  try {
-       str.resize(size);
-
-  } catch (const std::length_error&) {
+  // Limit to 32 MiB, used in many clients.
+  if (remaining > 1 << 25)
     return false;
-  }
 
-  for (auto& c : str) {
-    if (!input->good())
-      break;
-    c = input->get();
+  str.clear();
+
+  while (remaining != 0) {
+    // Read in chunks of 64 KiB, it is very unlikely that a bencode stream is 
really that large so
+    // we're assuming this fails at some point.
+    uint32_t read_size = std::min<uint32_t>(remaining, 1 << 16);
+
+    str.resize(str.size() + read_size);
+
+    input->read(str.data() + str.size() - read_size, read_size);
+
+    if (input->gcount() != read_size)
+      return false;
+
+    remaining -= read_size;
   }
 
   return !input->fail();
@@ -40,45 +51,36 @@
 
 static const char*
 object_read_bencode_c_value(const char* first, const char* last, int64_t& 
value) {
-  if (first == last)
-    return first;
-
-  bool neg = false;
+  auto errc = std::from_chars(first, last, value, 10);
 
-  if (*first == '-') {
-    // Don't allow '-0', or '-' followed by non-numeral.
-    if ((first + 1) == last || *(first + 1) <= '0' || *(first + 1) > '9')
-      return first;
-
-    neg = true;
-    first++;
-  }
+  if (errc.ec != std::errc() || errc.ptr == first)
+    throw torrent::bencode_error("Invalid bencode data: invalid integer.");
 
-  value = 0;
+  if (errc.ptr >= last || *errc.ptr != 'e')
+    throw torrent::bencode_error("Invalid bencode data: missing 'e' 
terminator.");
 
-  while (first != last && *first >= '0' && *first <= '9')
-    value = value * 10 + (*first++ - '0');
+  if (value != 0 && *first == '0')
+    throw torrent::bencode_error("Invalid bencode data: leading zeros are not 
allowed.");
 
-  if (neg)
-    value = -value;
+  if (value == 0 && *first == '-')
+    throw torrent::bencode_error("Invalid bencode data: negative zero is not 
allowed.");
 
-  return first;
+  return errc.ptr + 1;
 }
 
 raw_string
 object_read_bencode_c_string(const char* first, const char* last) {
-  // Set the most-significant bit so that if there are no numbers in
-  // the input it will fail the length check, while "0" will shift the
-  // bit out.
-  unsigned int length = 0x1U << (std::numeric_limits<unsigned int>::digits - 
1);
+  uint32_t length{};
 
-  while (first != last && *first >= '0' && *first <= '9')
-    length = length * 10 + (*first++ - '0');
+  auto errc = std::from_chars(first, last, length, 10);
 
-  if (length + 1 > static_cast<unsigned int>(std::distance(first, last)) || 
length + 1 == 0 || *first++ != ':')
-    throw torrent::bencode_error("Invalid bencode data.");
+  if (errc.ec != std::errc() || errc.ptr == first || errc.ptr == last || 
*errc.ptr != ':')
+    throw torrent::bencode_error("Invalid bencode data: string length is 
invalid.");
 
-  return raw_string(first, length);
+  if (std::distance(errc.ptr + 1, last) < static_cast<std::ptrdiff_t>(length))
+    throw torrent::bencode_error("Invalid bencode data: string length exceeds 
available data.");
+
+  return raw_string(errc.ptr + 1, length);
 }
 
 // Could consider making this non-recursive, but they seldomly are
@@ -185,12 +187,7 @@
   switch (*first) {
   case 'i':
     *object = Object::create_value();
-    first = object_read_bencode_c_value(first + 1, last, object->as_value());
-
-    if (first == last || *first++ != 'e')
-      break;
-
-    return first;
+    return object_read_bencode_c_value(first + 1, last, object->as_value());
 
   case 'l':
     if (++depth >= 1024)
@@ -434,19 +431,23 @@
   if (src == 0)
     return object_write_bencode_c_char(output, '0');
 
+  uint64_t value;
+
   if (src < 0) {
     object_write_bencode_c_char(output, '-');
-    src = -src;
+    value = -static_cast<uint64_t>(src);
+  } else {
+    value = static_cast<uint64_t>(src);
   }
 
   char buffer[20];
   char* first = buffer + 20;
 
   // We don't need locale support, so just do this directly.
-  while (src != 0) {
-    *--first = '0' + src % 10;
+  while (value != 0) {
+    *--first = '0' + value % 10;
 
-    src /= 10;
+    value /= 10;
   }
 
   object_write_bencode_c_string(output, first, 20 - std::distance(buffer, 
first));
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/path.cc 
new/libtorrent-0.16.21/src/torrent/path.cc
--- old/libtorrent-0.16.20/src/torrent/path.cc  2026-08-06 10:14:08.000000000 
+0200
+++ new/libtorrent-0.16.21/src/torrent/path.cc  2026-08-26 09:17:41.000000000 
+0200
@@ -23,6 +23,19 @@
   }
 }
 
+bool
+Path::compare_less(const Path* left, const Path* right) {
+  return std::lexicographical_compare(left->begin(), left->end(), 
right->begin(), right->end(),
+                                      [](const auto& l, const auto& r) { 
return l.str() < r.str(); });
+}
+
+bool
+Path::is_prefix(const Path* prefix, const Path* path) {
+  return prefix->size() <= path->size() &&
+    std::equal(prefix->begin(), prefix->end(), path->begin(),
+               [](const auto& l, const auto& r) { return l.str() == r.str(); 
});
+}
+
 std::string
 Path::as_string() const {
   if (empty())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/path.h 
new/libtorrent-0.16.21/src/torrent/path.h
--- old/libtorrent-0.16.20/src/torrent/path.h   2026-08-06 10:14:08.000000000 
+0200
+++ new/libtorrent-0.16.21/src/torrent/path.h   2026-08-26 09:17:41.000000000 
+0200
@@ -36,6 +36,9 @@
 
   base_type*         base()                               { return this; }
   const base_type*   base() const                         { return this; }
+
+  static bool        compare_less(const Path* left, const Path* right);
+  static bool        is_prefix(const Path* prefix, const Path* path);
 };
 
 inline void Path::push_back(const std::string& path) { insert_path(end(), 
path); }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libtorrent-0.16.20/src/torrent/runtime/network_config.cc 
new/libtorrent-0.16.21/src/torrent/runtime/network_config.cc
--- old/libtorrent-0.16.20/src/torrent/runtime/network_config.cc        
2026-08-06 10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/torrent/runtime/network_config.cc        
2026-08-26 09:17:41.000000000 +0200
@@ -264,6 +264,53 @@
   return sa_addr_str(m_local_inet6_address.get());
 }
 
+uint16_t
+NetworkConfig::local_inet_port() const {
+  auto guard = lock_guard();
+  return m_local_inet_port;
+}
+
+uint16_t
+NetworkConfig::local_inet6_port() const {
+  auto guard = lock_guard();
+  return m_local_inet6_port;
+}
+
+uint16_t
+NetworkConfig::local_port_for_family(int family) const {
+  auto guard = lock_guard();
+
+  switch (family) {
+  case AF_INET:  return m_local_inet_port;
+  case AF_INET6: return m_local_inet6_port;
+  default:
+    throw input_error("NetworkConfig::local_port_for_family() called with 
invalid address family");
+  }
+}
+
+uint16_t
+NetworkConfig::local_port_best_match() const {
+  auto guard = lock_guard();
+
+  if (m_local_inet_port == 0)
+    return m_local_inet6_port;
+
+  if (m_local_inet6_port == 0)
+    return m_local_inet_port;
+
+  if (m_prefer_ipv6) {
+    if (m_block_ipv6 && !m_block_ipv4)
+      return m_local_inet_port;
+
+    return m_local_inet6_port;
+  }
+
+  if (m_block_ipv4 && !m_block_ipv6)
+    return m_local_inet6_port;
+
+  return m_local_inet_port;
+}
+
 void
 NetworkConfig::set_bind_address(const sockaddr* sa) {
   auto guard = lock_guard();
@@ -346,6 +393,25 @@
 }
 
 void
+NetworkConfig::set_local_inet_port(uint16_t port) {
+  auto guard = lock_guard();
+  m_local_inet_port = port;
+}
+
+void
+NetworkConfig::set_local_inet6_port(uint16_t port) {
+  auto guard = lock_guard();
+  m_local_inet6_port = port;
+}
+
+void
+NetworkConfig::set_local_port(uint16_t port) {
+  auto guard = lock_guard();
+  m_local_inet_port  = port;
+  m_local_inet6_port = port;
+}
+
+void
 NetworkConfig::set_encryption_modes(encryption_mode handshake, encryption_mode 
stream) {
   if (handshake == ENCRYPTION_MODE_DENY && stream == ENCRYPTION_MODE_REQUIRE)
     throw internal_error("Invalid encryption modes: 
handshake_deny/stream_require");
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libtorrent-0.16.20/src/torrent/runtime/network_config.h 
new/libtorrent-0.16.21/src/torrent/runtime/network_config.h
--- old/libtorrent-0.16.20/src/torrent/runtime/network_config.h 2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/torrent/runtime/network_config.h 2026-08-26 
09:17:41.000000000 +0200
@@ -82,6 +82,13 @@
   c_sa_shared_ptr     local_inet6_address_or_null() const;
   std::string         local_inet6_address_str() const;
 
+  // Ports reported to trackers, parallel to the local addresses above. A port 
of 0 means unset,
+  // in which case trackers fall back to reporting runtime::listen_port().
+  uint16_t            local_inet_port() const;
+  uint16_t            local_inet6_port() const;
+  uint16_t            local_port_for_family(int family) const;
+  uint16_t            local_port_best_match() const;
+
   void                set_bind_address(const sockaddr* sa);
   void                set_bind_address_str(const std::string& addr);
   void                set_bind_inet_address(const sockaddr* sa);
@@ -95,6 +102,10 @@
   void                set_local_inet6_address(const sockaddr* sa);
   void                set_local_inet6_address_str(const std::string& addr);
 
+  void                set_local_inet_port(uint16_t port);
+  void                set_local_inet6_port(uint16_t port);
+  void                set_local_port(uint16_t port);
+
   int                 listen_backlog() const;
   void                set_listen_backlog(int backlog);
 
@@ -170,6 +181,9 @@
   c_sa_shared_ptr     m_local_inet_address;
   c_sa_shared_ptr     m_local_inet6_address;
 
+  uint16_t            m_local_inet_port{0};
+  uint16_t            m_local_inet6_port{0};
+
   int                 m_listen_backlog{SOMAXCONN};
   uint16_t            m_override_dht_port{0};
   uint32_t            m_send_buffer_size{0};
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libtorrent-0.16.20/src/torrent/runtime/network_manager.cc 
new/libtorrent-0.16.21/src/torrent/runtime/network_manager.cc
--- old/libtorrent-0.16.20/src/torrent/runtime/network_manager.cc       
2026-08-06 10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/torrent/runtime/network_manager.cc       
2026-08-26 09:17:41.000000000 +0200
@@ -264,7 +264,7 @@
     listen_open_unsafe(m_listen_port, m_listen_port);
 
   } catch (const base_error& e) {
-    LT_LOG_NOTICE("Could not restart listen socket: %" PRIu16 " : %s", 
e.what());
+    LT_LOG_NOTICE("Could not restart listen socket: %" PRIu16 " : %s", 
m_listen_port, e.what());
     return;
   }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libtorrent-0.16.20/src/torrent/runtime/socket_manager.cc 
new/libtorrent-0.16.21/src/torrent/runtime/socket_manager.cc
--- old/libtorrent-0.16.20/src/torrent/runtime/socket_manager.cc        
2026-08-06 10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/torrent/runtime/socket_manager.cc        
2026-08-26 09:17:41.000000000 +0200
@@ -20,6 +20,25 @@
 constexpr uint32_t allocation_headroom = 8;
 
 uint32_t
+calculate_alloc_limit(torrent::runtime::socket_manager_category_t category) {
+  if (category == torrent::runtime::category_http)
+    return torrent::runtime::SocketManager::http_max_alloc;
+
+  if (category == torrent::runtime::category_files)
+    return torrent::runtime::SocketManager::files_max_alloc;
+
+  return torrent::runtime::SocketManager::category_max_alloc;
+}
+
+uint32_t
+calculate_alloc_minimum(torrent::runtime::socket_manager_category_t category) {
+  if (category == torrent::runtime::category_files)
+    return torrent::runtime::SocketManager::files_min_alloc;
+
+  return 0;
+}
+
+uint32_t
 calculate_min_generic(uint32_t open_max) {
   if (open_max >= 16384)
     return 12288;
@@ -127,6 +146,16 @@
 }
 
 uint32_t
+SocketManager::category_alloc_limit(category_t category) {
+  return calculate_alloc_limit(category);
+}
+
+uint32_t
+SocketManager::category_alloc_minimum(category_t category) {
+  return calculate_alloc_minimum(category);
+}
+
+uint32_t
 SocketManager::generic_min_allocation() {
   return calculate_min_generic(m_max_size);
 }
@@ -208,6 +237,14 @@
       allocation = std::max(allocation, 
m_category_min_alloc[static_cast<uint32_t>(category)].load());
       allocation = std::min(allocation, 
m_category_max_alloc[static_cast<uint32_t>(category)].load());
 
+      if (allocation > calculate_alloc_limit(category))
+        throw input_error("adjust_allocation: allocation exceeds the category 
limit : " +
+                          std::to_string(allocation) + " > " + 
std::to_string(calculate_alloc_limit(category)));
+
+      if (allocation < calculate_alloc_minimum(category))
+        throw input_error("adjust_allocation: allocation below the category 
minimum : " +
+                          std::to_string(allocation) + " < " + 
std::to_string(calculate_alloc_minimum(category)));
+
       total_allocated                               += allocation;
       new_max_size[static_cast<uint32_t>(category)]  = allocation;
     };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libtorrent-0.16.20/src/torrent/runtime/socket_manager.h 
new/libtorrent-0.16.21/src/torrent/runtime/socket_manager.h
--- old/libtorrent-0.16.20/src/torrent/runtime/socket_manager.h 2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/torrent/runtime/socket_manager.h 2026-08-26 
09:17:41.000000000 +0200
@@ -53,6 +53,9 @@
 public:
   static constexpr uint32_t category_count     = 5;
   static constexpr uint32_t category_max_alloc = 1000000;
+  static constexpr uint32_t http_max_alloc     = 4096;
+  static constexpr uint32_t files_max_alloc    = 1 << 16;
+  static constexpr uint32_t files_min_alloc    = 4;
   static constexpr int      flag_inactive = (1 << 0);
 
   using category_t = socket_manager_category_t;
@@ -66,6 +69,9 @@
   uint32_t            category_min_allocation(category_t category);
   uint32_t            category_max_allocation(category_t category);
 
+  uint32_t            category_alloc_limit(category_t category);
+  uint32_t            category_alloc_minimum(category_t category);
+
   uint32_t            generic_min_allocation();
   uint32_t            reserved_allocation();
   uint32_t            available_allocation();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/system/poll_epoll.cc 
new/libtorrent-0.16.21/src/torrent/system/poll_epoll.cc
--- old/libtorrent-0.16.20/src/torrent/system/poll_epoll.cc     2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/torrent/system/poll_epoll.cc     2026-08-26 
09:17:41.000000000 +0200
@@ -13,6 +13,7 @@
 #include "torrent/net/fd.h"
 #include "torrent/system/event.h"
 #include "torrent/system/thread.h"
+#include "torrent/utils/chrono.h"
 #include "torrent/utils/log.h"
 
 #define LT_LOG(log_fmt, ...)                                        \
@@ -195,6 +196,8 @@
 Poll::do_poll(std::chrono::microseconds timeout) {
   int status = poll(timeout);
 
+  this_thread::thread()->set_cached_time(torrent::utils::time_since_epoch());
+
   if (status == -1) {
     if (errno != EINTR)
       throw internal_error("Poll::work() " + 
std::string(std::strerror(errno)));
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/system/poll_kqueue.cc 
new/libtorrent-0.16.21/src/torrent/system/poll_kqueue.cc
--- old/libtorrent-0.16.20/src/torrent/system/poll_kqueue.cc    2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/torrent/system/poll_kqueue.cc    2026-08-26 
09:17:41.000000000 +0200
@@ -15,6 +15,7 @@
 #include "torrent/net/fd.h"
 #include "torrent/system/event.h"
 #include "torrent/system/thread.h"
+#include "torrent/utils/chrono.h"
 
 // TODO: Change to use unordered_map, and at regular intervals trim the size?
 
@@ -196,6 +197,8 @@
 Poll::do_poll(std::chrono::microseconds timeout) {
   int status = poll(timeout);
 
+  this_thread::thread()->set_cached_time(torrent::utils::time_since_epoch());
+
   if (status == -1) {
     if (errno != EINTR)
       throw internal_error("Poll::do_poll() error: " + 
std::string(std::strerror(errno)));
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/tracker/tracker_http.cc 
new/libtorrent-0.16.21/src/tracker/tracker_http.cc
--- old/libtorrent-0.16.20/src/tracker/tracker_http.cc  2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/tracker/tracker_http.cc  2026-08-26 
09:17:41.000000000 +0200
@@ -313,7 +313,9 @@
   if (m_params.numwant >= 0 && state != tracker::TrackerState::EVENT_STOPPED)
     s << "&numwant=" << m_params.numwant;
 
-  s << "&port="       << runtime::listen_port()
+  auto local_port = runtime::network_config()->local_port_for_family(family);
+
+  s << "&port="       << (local_port != 0 ? local_port : 
runtime::listen_port())
     << "&uploaded="   << m_params.uploaded_adjusted
     << "&downloaded=" << m_params.completed_adjusted
     << "&left="       << m_params.download_left;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/tracker/tracker_udp.cc 
new/libtorrent-0.16.21/src/tracker/tracker_udp.cc
--- old/libtorrent-0.16.20/src/tracker/tracker_udp.cc   2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/tracker/tracker_udp.cc   2026-08-26 
09:17:41.000000000 +0200
@@ -298,7 +298,9 @@
 
   buffer.write_32(info().key);
   buffer.write_32(m_params.numwant);
-  buffer.write_16(runtime::listen_port());
+
+  auto local_port = runtime::network_config()->local_port_for_family(family);
+  buffer.write_16(local_port != 0 ? local_port : runtime::listen_port());
 
   if (buffer.size_end() != 98)
     throw internal_error("TrackerUdp::prepare_announce() unexpected buffer 
size.");
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/tracker/udp_router.cc 
new/libtorrent-0.16.21/src/tracker/udp_router.cc
--- old/libtorrent-0.16.20/src/tracker/udp_router.cc    2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/tracker/udp_router.cc    2026-08-26 
09:17:41.000000000 +0200
@@ -553,7 +553,7 @@
     }
 
     if (bytes_read == 0)
-      throw internal_error("UdpRouter::event_read() read datagram of length 
0");
+      continue;
 
     m_buffer.set_end(bytes_read);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/src/utils/diffie_hellman.cc 
new/libtorrent-0.16.21/src/utils/diffie_hellman.cc
--- old/libtorrent-0.16.20/src/utils/diffie_hellman.cc  2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/src/utils/diffie_hellman.cc  2026-08-26 
09:17:41.000000000 +0200
@@ -56,12 +56,25 @@
 DiffieHellman::compute_secret(const unsigned char *pubkey, unsigned int 
length) {
   BIGNUM* k = BN_bin2bn(pubkey, length, nullptr);
 
-  m_secret = std::make_unique<char[]>(DH_size(dh_get(m_dh)));
-  m_size = DH_compute_key(reinterpret_cast<unsigned char*>(m_secret.get()), k, 
dh_get(m_dh));
-  
+  int secret_size = DH_size(dh_get(m_dh));
+
+  m_secret = std::make_unique<char[]>(secret_size);
+
+  int computed_size = DH_compute_key(reinterpret_cast<unsigned 
char*>(m_secret.get()), k, dh_get(m_dh));
+
   BN_free(k);
 
-  return m_size != -1;
+  if (computed_size == -1)
+    return false;
+
+  if (computed_size < secret_size) {
+    std::memmove(m_secret.get() + secret_size - computed_size, m_secret.get(), 
computed_size);
+    std::memset(m_secret.get(), 0, secret_size - computed_size);
+  }
+
+  m_size = secret_size;
+
+  return true;
 }
 
 void
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libtorrent-0.16.20/test/torrent/object_stream_test.cc 
new/libtorrent-0.16.21/test/torrent/object_stream_test.cc
--- old/libtorrent-0.16.20/test/torrent/object_stream_test.cc   2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/test/torrent/object_stream_test.cc   2026-08-26 
09:17:42.000000000 +0200
@@ -195,3 +195,82 @@
   obj.as_map()["d"] = torrent::Object();
   CPPUNIT_ASSERT(object_write_bencode(obj, "d1:ai1e1:b4:test1:cl3:fooee"));
 }
+
+namespace {
+
+bool
+read_string_accepted(const char* input) {
+  try {
+    uint64_t string_length = std::strlen(input);
+
+    torrent::Object tmp;
+    auto last = torrent::object_read_bencode_c(input, input + string_length, 
&tmp);
+
+    return tmp.is_string() && last == input + string_length;
+
+  } catch (const torrent::bencode_error&) {
+    return false;
+  }
+}
+
+} // namespace anonymous
+
+void
+ObjectStreamTest::test_read_string_length() {
+  CPPUNIT_ASSERT(read_string_accepted("0:"));
+  CPPUNIT_ASSERT(read_string_accepted("1:a"));
+  CPPUNIT_ASSERT(read_string_accepted("4:abcd"));
+  CPPUNIT_ASSERT(read_string_accepted("10:abcdefghij"));
+
+  CPPUNIT_ASSERT(!read_string_accepted("4294967296:abcd"));
+  CPPUNIT_ASSERT(!read_string_accepted("4294967300:abcd"));
+  CPPUNIT_ASSERT(!read_string_accepted("18446744073709551616:abcd"));
+  CPPUNIT_ASSERT(!read_string_accepted(":abcd"));
+
+  CPPUNIT_ASSERT(!read_string_accepted("4:abc"));
+  CPPUNIT_ASSERT(!read_string_accepted("4:abcde"));
+  CPPUNIT_ASSERT(!read_string_accepted("-4:abcd"));
+
+  CPPUNIT_ASSERT(!read_string_accepted("0x4:abcd"));
+  CPPUNIT_ASSERT(!read_string_accepted("0X4:abcd"));
+  CPPUNIT_ASSERT(!read_string_accepted("a:abcdefghij"));
+}
+
+static bool
+read_value_ok(const char* input, int64_t expected) {
+  try {
+    torrent::Object tmp;
+    const char* last = input + std::strlen(input);
+
+    return torrent::object_read_bencode_c(input, last, &tmp) == last &&
+      tmp.is_value() && tmp.as_value() == expected;
+
+  } catch (const torrent::bencode_error&) {
+    return false;
+  }
+}
+
+static bool
+read_value_rejected(const char* input) {
+  try {
+    torrent::Object tmp;
+    torrent::object_read_bencode_c(input, input + std::strlen(input), &tmp);
+    return false;
+
+  } catch (const torrent::bencode_error&) {
+    return true;
+  }
+}
+
+void
+ObjectStreamTest::test_read_value_bounds() {
+  CPPUNIT_ASSERT(read_value_ok("i0e", 0));
+  CPPUNIT_ASSERT(read_value_ok("i-1e", -1));
+  CPPUNIT_ASSERT(read_value_ok("i9223372036854775807e", 
std::numeric_limits<int64_t>::max()));
+  CPPUNIT_ASSERT(read_value_ok("i-9223372036854775808e", 
std::numeric_limits<int64_t>::min()));
+
+  CPPUNIT_ASSERT(read_value_rejected("i9223372036854775808e"));
+  CPPUNIT_ASSERT(read_value_rejected("i-9223372036854775809e"));
+  CPPUNIT_ASSERT(read_value_rejected("i18446744073709551615e"));
+  CPPUNIT_ASSERT(read_value_rejected("i99999999999999999999e"));
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.20/test/torrent/object_stream_test.h 
new/libtorrent-0.16.21/test/torrent/object_stream_test.h
--- old/libtorrent-0.16.20/test/torrent/object_stream_test.h    2026-08-06 
10:14:08.000000000 +0200
+++ new/libtorrent-0.16.21/test/torrent/object_stream_test.h    2026-08-26 
09:17:42.000000000 +0200
@@ -9,6 +9,8 @@
   CPPUNIT_TEST(testOutputMask);
   CPPUNIT_TEST(testBuffer);
   CPPUNIT_TEST(testReadBencodeC);
+  CPPUNIT_TEST(test_read_string_length);
+  CPPUNIT_TEST(test_read_value_bounds);
 
   CPPUNIT_TEST(test_read_skip);
   CPPUNIT_TEST(test_read_skip_invalid);
@@ -22,6 +24,8 @@
   void testBuffer();
 
   void testReadBencodeC();
+  void test_read_string_length();
+  void test_read_value_bounds();
 
   void test_read_skip();
   void test_read_skip_invalid();

Reply via email to