diff --git a/include/asio/ssl/detail/impl/engine.ipp b/include/asio/ssl/detail/impl/engine.ipp index 814e11bcea..b81673e9d5 100644 --- a/include/asio/ssl/detail/impl/engine.ipp +++ b/include/asio/ssl/detail/impl/engine.ipp @@ -51,7 +51,7 @@ engine::engine(SSL_CTX* context) #endif // defined(SSL_MODE_RELEASE_BUFFERS) ::BIO* int_bio = 0; - ::BIO_new_bio_pair(&int_bio, 0, &ext_bio_, 0); + ::BIO_new_bio_pair(&int_bio, 128*1024, &ext_bio_, 128*1024); ::SSL_set_bio(ssl_, int_bio, int_bio); } @@ -69,7 +69,7 @@ engine::engine(SSL* ssl_impl) #endif // defined(SSL_MODE_RELEASE_BUFFERS) ::BIO* int_bio = 0; - ::BIO_new_bio_pair(&int_bio, 0, &ext_bio_, 0); + ::BIO_new_bio_pair(&int_bio, 128*1024, &ext_bio_, 128*1024); ::SSL_set_bio(ssl_, int_bio, int_bio); } diff --git a/include/asio/ssl/detail/read_op.hpp b/include/asio/ssl/detail/read_op.hpp index 36b623fc29..a61e2e19e4 100644 --- a/include/asio/ssl/detail/read_op.hpp +++ b/include/asio/ssl/detail/read_op.hpp @@ -44,11 +44,61 @@ class read_op asio::error_code& ec, std::size_t& bytes_transferred) const { + bytes_transferred = 0; + asio::mutable_buffer buffer = asio::detail::buffer_sequence_adapter::first(buffers_); - return eng.read(buffer, ec, bytes_transferred); + while (true) + { + asio::mutable_buffer current_buffer = buffer + bytes_transferred; + + // If user buffer is full, we are done + if (current_buffer.size() == 0) + { + ec = asio::error_code(); + return engine::want_nothing; + } + + std::size_t bytes = 0; + engine::want w = eng.read(current_buffer, ec, bytes); + + bytes_transferred += bytes; + + // If an error occurred but we already got data in this call, return the + // data first. This avoids dropping trailing bytes when the peer closes. + // If the error persists, it will be reraised in the next call. + if (ec) + { + if ((ec == asio::error::eof) && (bytes_transferred > 0)) + { + ec = asio::error_code(); + return engine::want_nothing; + } + return w; + } + + switch (w) + { + case engine::want_nothing: + // If we got bytes, LOOP AGAIN to see if more data is waiting in the BIO. + if (bytes > 0) continue; + + // If 0 bytes (EOF/Shutdown), fall through to return result + [[fallthrough]]; + + default: + // If we have accumulated ANY data, treat this as success. + // This handles want_input, want_output, etc., by returning control + // to the caller to process the data before handling the SSL state. + if (bytes_transferred > 0) + { + return engine::want_nothing; + } + return w; + } + } } template diff --git a/include/asio/ssl/detail/stream_core.hpp b/include/asio/ssl/detail/stream_core.hpp index 3edf7ccee5..135a9818ff 100644 --- a/include/asio/ssl/detail/stream_core.hpp +++ b/include/asio/ssl/detail/stream_core.hpp @@ -29,9 +29,9 @@ namespace detail { struct stream_core { - // According to the OpenSSL documentation, this is the buffer size that is - // sufficient to hold the largest possible TLS record. - enum { max_tls_record_size = 17 * 1024 }; + // A TLS record requires a buffer size of 17KB at maximum. + // We further increase the buffer size to avoid small operations in the io_context. + enum { max_tls_record_size = 128 * 1024 }; template stream_core(SSL_CTX* context, const Executor& ex)