mirror of
https://github.com/hanzoai/datastore.git
synced 2026-09-26 13:55:07 +00:00
Backport #102894 to 26.4: Fix short writes in Poco BufferedStreamBuf::flushBuffer
This commit is contained in:
@@ -136,12 +136,16 @@ private:
|
||||
int flushBuffer()
|
||||
{
|
||||
int n = int(this->pptr() - this->pbase());
|
||||
if (writeToDevice(this->pbase(), n) == n)
|
||||
int offset = 0;
|
||||
while (offset < n)
|
||||
{
|
||||
this->pbump(-n);
|
||||
return n;
|
||||
int written = writeToDevice(this->pbase() + offset, n - offset);
|
||||
if (written <= 0)
|
||||
return -1;
|
||||
offset += written;
|
||||
}
|
||||
return -1;
|
||||
this->pbump(-n);
|
||||
return n;
|
||||
}
|
||||
|
||||
std::streamsize _bufsize;
|
||||
|
||||
@@ -134,12 +134,16 @@ private:
|
||||
int flushBuffer()
|
||||
{
|
||||
int n = int(this->pptr() - this->pbase());
|
||||
if (writeToDevice(this->pbase(), n) == n)
|
||||
int offset = 0;
|
||||
while (offset < n)
|
||||
{
|
||||
this->pbump(-n);
|
||||
return n;
|
||||
int written = writeToDevice(this->pbase() + offset, n - offset);
|
||||
if (written <= 0)
|
||||
return -1;
|
||||
offset += written;
|
||||
}
|
||||
return -1;
|
||||
this->pbump(-n);
|
||||
return n;
|
||||
}
|
||||
|
||||
std::streamsize _bufsize;
|
||||
|
||||
@@ -52,8 +52,8 @@ void HTTPChunkedStreamBuf::close()
|
||||
if (_mode & std::ios::out && _chunk != std::char_traits<char>::eof())
|
||||
{
|
||||
sync();
|
||||
_session.write("0\r\n\r\n", 5);
|
||||
|
||||
if (writeToDevice("", 0) < 0)
|
||||
throw MessageException("Failed to write terminating chunk");
|
||||
_chunk = std::char_traits<char>::eof();
|
||||
}
|
||||
}
|
||||
@@ -167,7 +167,16 @@ int HTTPChunkedStreamBuf::writeToDevice(const char* buffer, std::streamsize leng
|
||||
_chunkBuffer.append("\r\n", 2);
|
||||
_chunkBuffer.append(buffer, static_cast<std::string::size_type>(length));
|
||||
_chunkBuffer.append("\r\n", 2);
|
||||
_session.write(_chunkBuffer.data(), static_cast<std::streamsize>(_chunkBuffer.size()));
|
||||
|
||||
std::streamsize chunkSize = static_cast<std::streamsize>(_chunkBuffer.size());
|
||||
std::streamsize offset = 0;
|
||||
while (offset < chunkSize)
|
||||
{
|
||||
int written = _session.write(_chunkBuffer.data() + offset, chunkSize - offset);
|
||||
if (written <= 0)
|
||||
return -1;
|
||||
offset += written;
|
||||
}
|
||||
return static_cast<int>(length);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,14 +68,11 @@ int HTTPFixedLengthStreamBuf::readFromDevice(char* buffer, std::streamsize lengt
|
||||
|
||||
int HTTPFixedLengthStreamBuf::writeToDevice(const char* buffer, std::streamsize length)
|
||||
{
|
||||
int n = 0;
|
||||
if (_count < _length)
|
||||
{
|
||||
if (_count + length > _length)
|
||||
length = static_cast<std::streamsize>(_length - _count);
|
||||
n = _session.write(buffer, length);
|
||||
if (n > 0) _count += n;
|
||||
}
|
||||
if (_count + length > _length)
|
||||
throw MessageException("Write past Content-Length");
|
||||
|
||||
int n = _session.write(buffer, length);
|
||||
if (n > 0) _count += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
@@ -322,6 +322,8 @@ int SecureSocketImpl::sendBytes(const void* buffer, int length, int flags)
|
||||
|
||||
rc = handleError(rc);
|
||||
if (rc == 0) throw SSLConnectionUnexpectedlyClosedException();
|
||||
if (rc < 0 && _pSocket->getBlocking())
|
||||
throw Poco::TimeoutException("SSL_write timed out");
|
||||
}
|
||||
|
||||
_pSocket->useSendThrottlerBudget(rc);
|
||||
@@ -364,7 +366,10 @@ int SecureSocketImpl::receiveBytes(void* buffer, int length, int flags)
|
||||
while (mustRetry(rc, remaining_time));
|
||||
if (rc <= 0)
|
||||
{
|
||||
return handleError(rc);
|
||||
rc = handleError(rc);
|
||||
if (rc < 0 && _pSocket->getBlocking())
|
||||
throw Poco::TimeoutException("SSL_read timed out");
|
||||
return rc;
|
||||
}
|
||||
|
||||
_pSocket->useRecvThrottlerBudget(rc);
|
||||
@@ -398,7 +403,10 @@ int SecureSocketImpl::completeHandshake()
|
||||
while (mustRetry(rc, remaining_time));
|
||||
if (rc <= 0)
|
||||
{
|
||||
return handleError(rc);
|
||||
rc = handleError(rc);
|
||||
if (rc < 0 && _pSocket->getBlocking())
|
||||
throw Poco::TimeoutException("SSL handshake timed out");
|
||||
return rc;
|
||||
}
|
||||
_needHandshake = false;
|
||||
return rc;
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <Poco/BufferedStreamBuf.h>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
|
||||
/// Mock BufferedStreamBuf that simulates short writes from the device.
|
||||
/// Each call to writeToDevice writes at most `max_write_size` bytes.
|
||||
class ShortWriteStreamBuf : public Poco::BufferedStreamBuf
|
||||
{
|
||||
public:
|
||||
ShortWriteStreamBuf(int buffer_size, int max_write_size_)
|
||||
: Poco::BufferedStreamBuf(buffer_size, std::ios::out)
|
||||
, max_write_size(max_write_size_)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string & written() const { return output; }
|
||||
int writeCount() const { return write_calls; }
|
||||
|
||||
private:
|
||||
int writeToDevice(const char * buffer, std::streamsize length) override
|
||||
{
|
||||
++write_calls;
|
||||
int to_write = std::min(static_cast<int>(length), max_write_size);
|
||||
output.append(buffer, to_write);
|
||||
return to_write;
|
||||
}
|
||||
|
||||
std::string output;
|
||||
int max_write_size;
|
||||
int write_calls = 0;
|
||||
};
|
||||
|
||||
|
||||
/// Mock that fails after writing a certain number of bytes total.
|
||||
class FailAfterNStreamBuf : public Poco::BufferedStreamBuf
|
||||
{
|
||||
public:
|
||||
FailAfterNStreamBuf(int buffer_size, int fail_after_)
|
||||
: Poco::BufferedStreamBuf(buffer_size, std::ios::out)
|
||||
, fail_after(fail_after_)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string & written() const { return output; }
|
||||
|
||||
private:
|
||||
int writeToDevice(const char * buffer, std::streamsize length) override
|
||||
{
|
||||
if (total_written >= fail_after)
|
||||
return 0;
|
||||
|
||||
int to_write = std::min(static_cast<int>(length), fail_after - total_written);
|
||||
output.append(buffer, to_write);
|
||||
total_written += to_write;
|
||||
return to_write;
|
||||
}
|
||||
|
||||
std::string output;
|
||||
int fail_after;
|
||||
int total_written = 0;
|
||||
};
|
||||
|
||||
|
||||
TEST(BufferedStreamBuf, FlushHandlesShortWrites)
|
||||
{
|
||||
/// Buffer size 16, device writes at most 3 bytes per call.
|
||||
ShortWriteStreamBuf buf(16, 3);
|
||||
std::ostream os(&buf);
|
||||
|
||||
std::string data = "Hello, World!"; /// 13 bytes
|
||||
os.write(data.data(), data.size());
|
||||
os.flush();
|
||||
|
||||
ASSERT_TRUE(os.good()) << "Stream should be in good state after flush";
|
||||
ASSERT_EQ(buf.written(), data);
|
||||
/// 13 bytes with max 3 per call = at least 5 writeToDevice calls
|
||||
ASSERT_GE(buf.writeCount(), 5);
|
||||
}
|
||||
|
||||
|
||||
TEST(BufferedStreamBuf, FlushHandlesShortWritesSingleByte)
|
||||
{
|
||||
/// Worst case: device writes 1 byte at a time.
|
||||
ShortWriteStreamBuf buf(32, 1);
|
||||
std::ostream os(&buf);
|
||||
|
||||
std::string data = "Short write test with single byte device";
|
||||
os.write(data.data(), data.size());
|
||||
os.flush();
|
||||
|
||||
ASSERT_TRUE(os.good());
|
||||
ASSERT_EQ(buf.written(), data);
|
||||
ASSERT_EQ(buf.writeCount(), static_cast<int>(data.size()));
|
||||
}
|
||||
|
||||
|
||||
TEST(BufferedStreamBuf, FlushHandlesFullWriteInOneCall)
|
||||
{
|
||||
/// Device writes everything in one call -- loop should iterate once.
|
||||
ShortWriteStreamBuf buf(64, 1000);
|
||||
std::ostream os(&buf);
|
||||
|
||||
std::string data = "All at once";
|
||||
os.write(data.data(), data.size());
|
||||
os.flush();
|
||||
|
||||
ASSERT_TRUE(os.good());
|
||||
ASSERT_EQ(buf.written(), data);
|
||||
ASSERT_EQ(buf.writeCount(), 1);
|
||||
}
|
||||
|
||||
|
||||
TEST(BufferedStreamBuf, FlushSetsErrorOnWriteFailure)
|
||||
{
|
||||
/// Device fails immediately (returns 0).
|
||||
FailAfterNStreamBuf buf(16, 0);
|
||||
std::ostream os(&buf);
|
||||
os.exceptions(std::ios::badbit);
|
||||
|
||||
std::string data = "Will fail";
|
||||
os.write(data.data(), data.size());
|
||||
|
||||
ASSERT_THROW(os.flush(), std::ios_base::failure);
|
||||
}
|
||||
|
||||
|
||||
TEST(BufferedStreamBuf, FlushWritesPartialThenFails)
|
||||
{
|
||||
/// Device writes 5 bytes then fails.
|
||||
FailAfterNStreamBuf buf(32, 5);
|
||||
std::ostream os(&buf);
|
||||
os.exceptions(std::ios::badbit);
|
||||
|
||||
std::string data = "1234567890"; /// 10 bytes, device fails after 5
|
||||
os.write(data.data(), data.size());
|
||||
|
||||
ASSERT_THROW(os.flush(), std::ios_base::failure);
|
||||
/// The first 5 bytes should have been written before failure.
|
||||
ASSERT_EQ(buf.written(), "12345");
|
||||
}
|
||||
|
||||
|
||||
TEST(BufferedStreamBuf, OverflowTriggersShortWriteLoop)
|
||||
{
|
||||
/// Buffer size 8, device writes 3 bytes at a time.
|
||||
/// Writing 20 bytes should trigger overflow (flushing the 8-byte buffer
|
||||
/// via the short-write loop) then continue filling the buffer.
|
||||
ShortWriteStreamBuf buf(8, 3);
|
||||
std::ostream os(&buf);
|
||||
|
||||
std::string data = "01234567ABCDEFGHIJKL"; /// 20 bytes
|
||||
os.write(data.data(), data.size());
|
||||
os.flush();
|
||||
|
||||
ASSERT_TRUE(os.good());
|
||||
ASSERT_EQ(buf.written(), data);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <Poco/Net/HTTPFixedLengthStream.h>
|
||||
#include <Poco/Net/HTTPClientSession.h>
|
||||
#include <Poco/Net/ServerSocket.h>
|
||||
#include <Poco/Net/StreamSocket.h>
|
||||
#include <Poco/Net/SocketAddress.h>
|
||||
#include <Poco/Net/NetException.h>
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/// A listening socket that never accepts. The kernel's listen backlog
|
||||
/// completes the TCP handshake, so a client can connect and write small
|
||||
/// amounts of data (absorbed by the kernel send buffer) without a server
|
||||
/// thread.
|
||||
class PassiveListener
|
||||
{
|
||||
public:
|
||||
PassiveListener() : server_socket(Poco::Net::SocketAddress("127.0.0.1", 0), 1) {}
|
||||
|
||||
Poco::Net::StreamSocket connect()
|
||||
{
|
||||
Poco::Net::StreamSocket sock;
|
||||
sock.connect(Poco::Net::SocketAddress("127.0.0.1", server_socket.address().port()));
|
||||
return sock;
|
||||
}
|
||||
|
||||
private:
|
||||
Poco::Net::ServerSocket server_socket;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Writing exactly Content-Length bytes should succeed.
|
||||
TEST(HTTPFixedLengthStreamBuf, WriteExactLength)
|
||||
{
|
||||
PassiveListener listener;
|
||||
Poco::Net::HTTPClientSession session(listener.connect());
|
||||
|
||||
Poco::Net::HTTPFixedLengthOutputStream stream(session, 10);
|
||||
|
||||
stream.write("0123456789", 10);
|
||||
stream.flush();
|
||||
|
||||
ASSERT_TRUE(stream.good()) << "Stream should be good after writing exactly Content-Length bytes";
|
||||
}
|
||||
|
||||
|
||||
/// Writing more than Content-Length should throw MessageException.
|
||||
TEST(HTTPFixedLengthStreamBuf, WriteOverLengthThrows)
|
||||
{
|
||||
PassiveListener listener;
|
||||
Poco::Net::HTTPClientSession session(listener.connect());
|
||||
|
||||
/// Content-Length is 5, but we will try to write 10 bytes.
|
||||
Poco::Net::HTTPFixedLengthOutputStream stream(session, 5);
|
||||
|
||||
/// The data goes into the 8KB buffer first. On flush, flushBuffer calls
|
||||
/// writeToDevice which clamps to Content-Length (writes 5 bytes), then the
|
||||
/// loop calls writeToDevice again with the remaining 5 bytes, which throws
|
||||
/// MessageException because _count >= _length.
|
||||
stream.write("0123456789", 10);
|
||||
|
||||
bool got_exception = false;
|
||||
try
|
||||
{
|
||||
stream.flush();
|
||||
}
|
||||
catch (const Poco::Net::MessageException &)
|
||||
{
|
||||
got_exception = true;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(got_exception) << "Expected MessageException when writing past Content-Length";
|
||||
}
|
||||
|
||||
|
||||
/// Writing exactly Content-Length and then one more byte should throw.
|
||||
TEST(HTTPFixedLengthStreamBuf, WriteBoundaryPlusOneThrows)
|
||||
{
|
||||
PassiveListener listener;
|
||||
Poco::Net::HTTPClientSession session(listener.connect());
|
||||
|
||||
Poco::Net::HTTPFixedLengthOutputStream stream(session, 5);
|
||||
|
||||
/// Write exactly 5 + 1 bytes.
|
||||
stream.write("012345", 6);
|
||||
|
||||
bool got_exception = false;
|
||||
try
|
||||
{
|
||||
stream.flush();
|
||||
}
|
||||
catch (const Poco::Net::MessageException &)
|
||||
{
|
||||
got_exception = true;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(got_exception) << "Expected MessageException when writing Content-Length + 1";
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
#include "config.h"
|
||||
|
||||
#if USE_SSL
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <Poco/Net/SecureServerSocket.h>
|
||||
#include <Poco/Net/SecureStreamSocket.h>
|
||||
#include <Poco/Net/Context.h>
|
||||
#include <Poco/Net/SSLException.h>
|
||||
#include <Poco/Net/SSLManager.h>
|
||||
#include <Poco/SharedPtr.h>
|
||||
#include <Poco/Timespan.h>
|
||||
#include <Poco/Exception.h>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/// Generate a self-signed certificate and private key in memory,
|
||||
/// write them to temporary files for Poco::Net::Context.
|
||||
struct EphemeralCert
|
||||
{
|
||||
std::string cert_path;
|
||||
std::string key_path;
|
||||
|
||||
EphemeralCert()
|
||||
{
|
||||
EVP_PKEY * pkey = EVP_RSA_gen(2048);
|
||||
if (!pkey)
|
||||
throw std::runtime_error("EVP_RSA_gen failed");
|
||||
|
||||
X509 * x509 = X509_new();
|
||||
if (!x509)
|
||||
{
|
||||
EVP_PKEY_free(pkey);
|
||||
throw std::runtime_error("X509_new failed");
|
||||
}
|
||||
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
|
||||
X509_gmtime_adj(X509_getm_notBefore(x509), 0);
|
||||
X509_gmtime_adj(X509_getm_notAfter(x509), 3600);
|
||||
X509_set_pubkey(x509, pkey);
|
||||
|
||||
X509_NAME * name = X509_get_subject_name(x509);
|
||||
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, reinterpret_cast<const unsigned char *>("localhost"), -1, -1, 0);
|
||||
X509_set_issuer_name(x509, name);
|
||||
X509_sign(x509, pkey, EVP_sha256());
|
||||
|
||||
cert_path = writeToTempFile(
|
||||
[&](BIO * bio) { PEM_write_bio_X509(bio, x509); }, "cert");
|
||||
key_path = writeToTempFile(
|
||||
[&](BIO * bio) { PEM_write_bio_PrivateKey(bio, pkey, nullptr, nullptr, 0, nullptr, nullptr); }, "key");
|
||||
|
||||
X509_free(x509);
|
||||
EVP_PKEY_free(pkey);
|
||||
}
|
||||
|
||||
~EphemeralCert()
|
||||
{
|
||||
(void)unlink(cert_path.c_str());
|
||||
(void)unlink(key_path.c_str());
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Fn>
|
||||
static std::string writeToTempFile(Fn writer, const char * suffix)
|
||||
{
|
||||
char path[256];
|
||||
(void)snprintf(path, sizeof(path), "/tmp/gtest_ssl_%s_XXXXXX", suffix);
|
||||
int fd = mkstemp(path);
|
||||
if (fd < 0)
|
||||
throw std::runtime_error("mkstemp failed");
|
||||
|
||||
BIO * bio = BIO_new_fd(fd, BIO_CLOSE);
|
||||
writer(bio);
|
||||
BIO_free(bio);
|
||||
return path;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Poco::Net::Context::Ptr makeContext(const EphemeralCert & cert, Poco::Net::Context::Usage usage)
|
||||
{
|
||||
Poco::Net::Context::Params params;
|
||||
params.privateKeyFile = cert.key_path;
|
||||
params.certificateFile = cert.cert_path;
|
||||
params.verificationMode = Poco::Net::Context::VERIFY_NONE;
|
||||
return new Poco::Net::Context(usage, params);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Test that a blocking SSL socket write throws TimeoutException
|
||||
/// when the peer stops reading and the send timeout expires.
|
||||
TEST(SSLSocketTimeout, SendBytesThrowsTimeoutOnBlockingSocket)
|
||||
{
|
||||
EphemeralCert cert;
|
||||
auto server_ctx = makeContext(cert, Poco::Net::Context::SERVER_USE);
|
||||
auto client_ctx = makeContext(cert, Poco::Net::Context::CLIENT_USE);
|
||||
|
||||
Poco::Net::SecureServerSocket server_socket(
|
||||
Poco::Net::SocketAddress("127.0.0.1", 0), 1, server_ctx);
|
||||
auto port = server_socket.address().port();
|
||||
|
||||
std::atomic<bool> server_done{false};
|
||||
|
||||
/// Server thread: accept and handshake, then sit idle (never read).
|
||||
std::thread server_thread([&]
|
||||
{
|
||||
try
|
||||
{
|
||||
auto accepted = server_socket.acceptConnection();
|
||||
/// Handshake happens on first I/O. Do a small read to trigger it.
|
||||
char buf[1];
|
||||
try { accepted.receiveBytes(buf, 1); } catch (...) {} /// Ok: handshake may fail. NOLINT(bugprone-empty-catch)
|
||||
/// Keep the connection open until the test completes.
|
||||
while (!server_done.load())
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
catch (...) {} /// Ok: server thread cleanup, test checks client-side behavior. NOLINT(bugprone-empty-catch)
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
Poco::Net::SecureStreamSocket client(
|
||||
Poco::Net::SocketAddress("127.0.0.1", port), client_ctx);
|
||||
|
||||
/// Very short send timeout so the test doesn't wait long.
|
||||
client.setSendTimeout(Poco::Timespan(0, 200'000)); /// 200ms
|
||||
|
||||
/// Write enough data to fill the TCP send buffer and SSL buffer.
|
||||
/// Typical TCP buffer is 128KB-256KB. Write 4MB to be sure.
|
||||
std::vector<char> data(4 * 1024 * 1024, 'X');
|
||||
|
||||
bool got_timeout = false;
|
||||
try
|
||||
{
|
||||
size_t offset = 0;
|
||||
while (offset < data.size())
|
||||
{
|
||||
int sent = client.sendBytes(data.data() + offset, static_cast<int>(data.size() - offset));
|
||||
if (sent > 0)
|
||||
offset += sent;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (const Poco::TimeoutException &)
|
||||
{
|
||||
got_timeout = true;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(got_timeout) << "Expected Poco::TimeoutException when writing to a non-reading SSL peer";
|
||||
}
|
||||
catch (const Poco::Exception & e)
|
||||
{
|
||||
/// Connection setup can fail on some systems; skip gracefully.
|
||||
/// Clean up the server thread before GTEST_SKIP returns from the function.
|
||||
server_done.store(true);
|
||||
server_socket.close();
|
||||
server_thread.join();
|
||||
GTEST_SKIP() << "SSL setup failed: " << e.displayText();
|
||||
}
|
||||
|
||||
server_done.store(true);
|
||||
/// Close the listening socket to unblock acceptConnection if the client
|
||||
/// failed before connecting (e.g. SSL context error).
|
||||
server_socket.close();
|
||||
server_thread.join();
|
||||
}
|
||||
|
||||
|
||||
/// Test that SSL handshake throws TimeoutException when the peer
|
||||
/// is a plain TCP listener that never speaks SSL.
|
||||
/// No server thread needed -- the kernel's listen backlog completes the
|
||||
/// TCP handshake, but nobody reads the accepted socket so the SSL
|
||||
/// ClientHello gets no response.
|
||||
TEST(SSLSocketTimeout, HandshakeThrowsTimeoutOnNonSSLPeer)
|
||||
{
|
||||
EphemeralCert cert;
|
||||
auto client_ctx = makeContext(cert, Poco::Net::Context::CLIENT_USE);
|
||||
|
||||
/// Listen but never accept -- kernel backlog handles TCP handshake.
|
||||
Poco::Net::ServerSocket listener(Poco::Net::SocketAddress("127.0.0.1", 0), 1);
|
||||
auto port = listener.address().port();
|
||||
|
||||
bool got_timeout = false;
|
||||
try
|
||||
{
|
||||
Poco::Net::StreamSocket raw_sock;
|
||||
raw_sock.connect(Poco::Net::SocketAddress("127.0.0.1", port));
|
||||
raw_sock.setSendTimeout(Poco::Timespan(0, 200'000));
|
||||
raw_sock.setReceiveTimeout(Poco::Timespan(0, 200'000));
|
||||
|
||||
Poco::Net::SecureStreamSocket ssl_sock(
|
||||
Poco::Net::SecureStreamSocket::attach(raw_sock, client_ctx));
|
||||
|
||||
/// completeHandshake is triggered by the first I/O.
|
||||
/// The peer won't respond with ServerHello, so it will time out.
|
||||
char buf[1] = {'X'};
|
||||
ssl_sock.sendBytes(buf, 1);
|
||||
}
|
||||
catch (const Poco::TimeoutException &)
|
||||
{
|
||||
got_timeout = true;
|
||||
}
|
||||
catch (const Poco::Exception &)
|
||||
{
|
||||
/// Some SSL implementations may throw a different SSL error
|
||||
/// before the timeout fires. That's acceptable -- the key thing
|
||||
/// is we don't silently return -1.
|
||||
got_timeout = true;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(got_timeout) << "Expected exception when SSL handshake times out against a non-SSL peer";
|
||||
}
|
||||
|
||||
|
||||
#endif /// USE_SSL
|
||||
Reference in New Issue
Block a user