aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVincent Richard <[email protected]>2014-02-05 19:18:20 +0000
committerVincent Richard <[email protected]>2014-02-05 19:18:20 +0000
commit0d20ee083b69d0cbde5a57a745386b987752af46 (patch)
treebbcbc887fc23cb6e6b2fdc05acd446ce89921319
parentMerge branch 'master' of https://github.com/kisli/vmime (diff)
downloadvmime-0d20ee083b69d0cbde5a57a745386b987752af46.tar.gz
vmime-0d20ee083b69d0cbde5a57a745386b987752af46.zip
Better polling. Fixed possible connection issues on POSIX with slow network. Better error handling in POSIX sockets.
-rw-r--r--src/vmime/net/imap/IMAPParser.hpp12
-rw-r--r--src/vmime/net/pop3/POP3Response.cpp12
-rw-r--r--src/vmime/net/smtp/SMTPResponse.cpp2
-rw-r--r--src/vmime/net/socket.hpp23
-rw-r--r--src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.cpp123
-rw-r--r--src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp5
-rw-r--r--src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp62
-rw-r--r--src/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp5
-rw-r--r--src/vmime/platform.hpp7
-rw-r--r--src/vmime/platforms/posix/posixHandler.cpp18
-rw-r--r--src/vmime/platforms/posix/posixHandler.hpp2
-rw-r--r--src/vmime/platforms/posix/posixSocket.cpp178
-rw-r--r--src/vmime/platforms/posix/posixSocket.hpp5
-rw-r--r--src/vmime/platforms/windows/windowsHandler.cpp6
-rw-r--r--src/vmime/platforms/windows/windowsHandler.hpp2
-rw-r--r--src/vmime/platforms/windows/windowsSocket.cpp79
-rw-r--r--src/vmime/platforms/windows/windowsSocket.hpp12
-rw-r--r--src/vmime/security/sasl/SASLSocket.cpp12
-rw-r--r--src/vmime/security/sasl/SASLSocket.hpp3
-rw-r--r--tests/testUtils.cpp12
-rw-r--r--tests/testUtils.hpp3
21 files changed, 373 insertions, 210 deletions
diff --git a/src/vmime/net/imap/IMAPParser.hpp b/src/vmime/net/imap/IMAPParser.hpp
index 533f78fb..4ea9cb57 100644
--- a/src/vmime/net/imap/IMAPParser.hpp
+++ b/src/vmime/net/imap/IMAPParser.hpp
@@ -5748,7 +5748,11 @@ public:
if (receiveBuffer.empty()) // buffer is empty
{
- platform::getHandler()->wait();
+ if (sok->getStatus() & socket::STATUS_WANT_WRITE)
+ sok->waitForWrite();
+ else
+ sok->waitForRead();
+
continue;
}
@@ -5807,7 +5811,11 @@ public:
if (receiveBuffer.empty()) // buffer is empty
{
- platform::getHandler()->wait();
+ if (sok->getStatus() & socket::STATUS_WANT_WRITE)
+ sok->waitForWrite();
+ else
+ sok->waitForRead();
+
continue;
}
diff --git a/src/vmime/net/pop3/POP3Response.cpp b/src/vmime/net/pop3/POP3Response.cpp
index 1dc5ee76..a3961200 100644
--- a/src/vmime/net/pop3/POP3Response.cpp
+++ b/src/vmime/net/pop3/POP3Response.cpp
@@ -178,7 +178,11 @@ void POP3Response::readResponseImpl(string& buffer, const bool multiLine)
if (receiveBuffer.empty()) // buffer is empty
{
- platform::getHandler()->wait();
+ if (m_socket->getStatus() & socket::STATUS_WANT_WRITE)
+ m_socket->waitForWrite();
+ else
+ m_socket->waitForRead();
+
continue;
}
@@ -269,7 +273,11 @@ void POP3Response::readResponseImpl
if (read == 0) // buffer is empty
{
- platform::getHandler()->wait();
+ if (m_socket->getStatus() & socket::STATUS_WANT_WRITE)
+ m_socket->waitForWrite();
+ else
+ m_socket->waitForRead();
+
continue;
}
diff --git a/src/vmime/net/smtp/SMTPResponse.cpp b/src/vmime/net/smtp/SMTPResponse.cpp
index f7980351..3d8bf15c 100644
--- a/src/vmime/net/smtp/SMTPResponse.cpp
+++ b/src/vmime/net/smtp/SMTPResponse.cpp
@@ -160,7 +160,7 @@ const string SMTPResponse::readResponseLine()
if (receiveBuffer.empty()) // buffer is empty
{
- platform::getHandler()->wait();
+ m_socket->waitForRead();
continue;
}
diff --git a/src/vmime/net/socket.hpp b/src/vmime/net/socket.hpp
index 7f878a73..72f0445f 100644
--- a/src/vmime/net/socket.hpp
+++ b/src/vmime/net/socket.hpp
@@ -49,7 +49,9 @@ public:
enum Status
{
- STATUS_WOULDBLOCK = 0x1 /**< The receive operation would block. */
+ STATUS_WOULDBLOCK = 0xf, /**< The operation would block. Retry later. */
+ STATUS_WANT_READ = 0x1, /**< The socket wants to read data, retry when data is available. */
+ STATUS_WANT_WRITE = 0x2 /**< The socket wants to write data, retry when data can be written. */
};
@@ -74,6 +76,25 @@ public:
*/
virtual bool isConnected() const = 0;
+ /** Block until new data is available for reading. The function will
+ * timeout after msecs milliseconds.
+ *
+ * @param timeout maximum wait time, in milliseconds (default is 30000);
+ * resolution is 10ms
+ * @return true if data is available, or false if the operation timed out
+ */
+ virtual bool waitForRead(const int msecs = 30000) = 0;
+
+ /** Block until pending data has been written and new data can be written.
+ * The function will timeout after msecs milliseconds.
+ *
+ * @param timeout maximum wait time, in milliseconds (default is 30000);
+ * resolution is 10ms
+ * @return true if new data can be written immediately, or false if the
+ * operation timed out
+ */
+ virtual bool waitForWrite(const int msecs = 30000) = 0;
+
/** Receive text data from the socket.
*
* @param buffer buffer in which to write received data
diff --git a/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.cpp b/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.cpp
index 13b7eb24..3832326c 100644
--- a/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.cpp
+++ b/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.cpp
@@ -30,6 +30,8 @@
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
+#include <errno.h>
+
#include "vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp"
#include "vmime/net/tls/gnutls/TLSSession_GnuTLS.hpp"
@@ -57,7 +59,7 @@ shared_ptr <TLSSocket> TLSSocket::wrap(shared_ptr <TLSSession> session, shared_p
TLSSocket_GnuTLS::TLSSocket_GnuTLS(shared_ptr <TLSSession_GnuTLS> session, shared_ptr <socket> sok)
: m_session(session), m_wrapped(sok), m_connected(false),
- m_handshaking(false), m_ex(NULL), m_status(0)
+ m_ex(NULL), m_status(0)
{
gnutls_transport_set_ptr(*m_session->m_gnutlsSession, this);
@@ -144,6 +146,18 @@ shared_ptr <timeoutHandler> TLSSocket_GnuTLS::getTimeoutHandler()
}
+bool TLSSocket_GnuTLS::waitForRead(const int msecs)
+{
+ return m_wrapped->waitForRead(msecs);
+}
+
+
+bool TLSSocket_GnuTLS::waitForWrite(const int msecs)
+{
+ return m_wrapped->waitForWrite(msecs);
+}
+
+
void TLSSocket_GnuTLS::receive(string& buffer)
{
const size_t size = receiveRaw(m_buffer, sizeof(m_buffer));
@@ -165,7 +179,7 @@ void TLSSocket_GnuTLS::send(const char* str)
size_t TLSSocket_GnuTLS::receiveRaw(byte_t* buffer, const size_t count)
{
- m_status &= ~STATUS_WOULDBLOCK;
+ m_status &= ~(STATUS_WANT_WRITE | STATUS_WANT_READ);
const ssize_t ret = gnutls_record_recv
(*m_session->m_gnutlsSession,
@@ -178,7 +192,11 @@ size_t TLSSocket_GnuTLS::receiveRaw(byte_t* buffer, const size_t count)
{
if (ret == GNUTLS_E_AGAIN)
{
- m_status |= STATUS_WOULDBLOCK;
+ if (gnutls_record_get_direction(*m_session->m_gnutlsSession) == 0)
+ m_status |= STATUS_WANT_READ;
+ else
+ m_status |= STATUS_WANT_WRITE;
+
return 0;
}
@@ -191,7 +209,7 @@ size_t TLSSocket_GnuTLS::receiveRaw(byte_t* buffer, const size_t count)
void TLSSocket_GnuTLS::sendRaw(const byte_t* buffer, const size_t count)
{
- m_status &= ~STATUS_WOULDBLOCK;
+ m_status &= ~(STATUS_WANT_WRITE | STATUS_WANT_READ);
for (size_t size = count ; size > 0 ; )
{
@@ -206,7 +224,11 @@ void TLSSocket_GnuTLS::sendRaw(const byte_t* buffer, const size_t count)
{
if (ret == GNUTLS_E_AGAIN)
{
- platform::getHandler()->wait();
+ if (gnutls_record_get_direction(*m_session->m_gnutlsSession) == 0)
+ m_wrapped->waitForRead();
+ else
+ m_wrapped->waitForWrite();
+
continue;
}
@@ -223,6 +245,8 @@ void TLSSocket_GnuTLS::sendRaw(const byte_t* buffer, const size_t count)
size_t TLSSocket_GnuTLS::sendRawNonBlocking(const byte_t* buffer, const size_t count)
{
+ m_status &= ~(STATUS_WANT_WRITE | STATUS_WANT_READ);
+
ssize_t ret = gnutls_record_send
(*m_session->m_gnutlsSession,
buffer, static_cast <size_t>(count));
@@ -234,7 +258,11 @@ size_t TLSSocket_GnuTLS::sendRawNonBlocking(const byte_t* buffer, const size_t c
{
if (ret == GNUTLS_E_AGAIN)
{
- m_status |= STATUS_WOULDBLOCK;
+ if (gnutls_record_get_direction(*m_session->m_gnutlsSession) == 0)
+ m_status |= STATUS_WANT_READ;
+ else
+ m_status |= STATUS_WANT_WRITE;
+
return 0;
}
@@ -259,8 +287,6 @@ void TLSSocket_GnuTLS::handshake()
toHandler->resetTimeOut();
// Start handshaking process
- m_handshaking = true;
-
try
{
while (true)
@@ -272,11 +298,17 @@ void TLSSocket_GnuTLS::handshake()
if (ret < 0)
{
- if (ret == GNUTLS_E_AGAIN ||
- ret == GNUTLS_E_INTERRUPTED)
+ if (ret == GNUTLS_E_AGAIN)
+ {
+ if (gnutls_record_get_direction(*m_session->m_gnutlsSession) == 0)
+ m_wrapped->waitForRead();
+ else
+ m_wrapped->waitForWrite();
+ }
+ else if (ret == GNUTLS_E_INTERRUPTED)
{
// Non-fatal error
- platform::getHandler()->wait();
+ m_wrapped->waitForRead();
}
else
{
@@ -292,12 +324,9 @@ void TLSSocket_GnuTLS::handshake()
}
catch (...)
{
- m_handshaking = false;
throw;
}
- m_handshaking = false;
-
// Verify server's certificate(s)
shared_ptr <security::cert::certificateChain> certs = getPeerCertificates();
@@ -321,14 +350,21 @@ ssize_t TLSSocket_GnuTLS::gnutlsPushFunc
(sok->m_wrapped->sendRawNonBlocking
(reinterpret_cast <const byte_t*>(data), len));
- if (ret == 0 && sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK)
- return GNUTLS_E_AGAIN;
+ if (ret == 0)
+ {
+ if (sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK)
+ gnutls_transport_set_errno(*sok->m_session->m_gnutlsSession, EAGAIN);
+ else
+ gnutls_transport_set_errno(*sok->m_session->m_gnutlsSession, 0);
+
+ return -1;
+ }
return ret;
}
catch (exception& e)
{
- // Workaround for bad behaviour when throwing C++ exceptions
+ // Workaround for non-portable behaviour when throwing C++ exceptions
// from C functions (GNU TLS)
sok->m_ex = e.clone();
return -1;
@@ -343,54 +379,25 @@ ssize_t TLSSocket_GnuTLS::gnutlsPullFunc
try
{
- // Workaround for cross-platform asynchronous handshaking:
- // gnutls_handshake() only returns GNUTLS_E_AGAIN if recv()
- // returns -1 and errno is set to EGAIN...
- if (sok->m_handshaking)
- {
- shared_ptr <timeoutHandler> toHandler = sok->m_wrapped->getTimeoutHandler();
-
- while (true)
- {
- const ssize_t ret = static_cast <ssize_t>
- (sok->m_wrapped->receiveRaw
- (reinterpret_cast <byte_t*>(data), len));
+ const ssize_t n = static_cast <ssize_t>
+ (sok->m_wrapped->receiveRaw
+ (reinterpret_cast <byte_t*>(data), len));
- if (ret == 0)
- {
- // No data available yet
- platform::getHandler()->wait();
- }
- else
- {
- return ret;
- }
-
- // Check whether the time-out delay is elapsed
- if (toHandler && toHandler->isTimeOut())
- {
- if (!toHandler->handleTimeOut())
- throw exceptions::operation_timed_out();
-
- toHandler->resetTimeOut();
- }
- }
- }
- else
+ if (n == 0)
{
- const ssize_t n = static_cast <ssize_t>
- (sok->m_wrapped->receiveRaw
- (reinterpret_cast <byte_t*>(data), len));
-
- if (n == 0 && sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK)
- return GNUTLS_E_AGAIN;
+ if (sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK)
+ gnutls_transport_set_errno(*sok->m_session->m_gnutlsSession, EAGAIN);
+ else
+ gnutls_transport_set_errno(*sok->m_session->m_gnutlsSession, 0);
- return n;
+ return -1;
}
+
+ return n;
}
catch (exception& e)
{
- // Workaround for bad behaviour when throwing C++ exceptions
+ // Workaround for non-portable behaviour when throwing C++ exceptions
// from C functions (GNU TLS)
sok->m_ex = e.clone();
return -1;
diff --git a/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp b/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp
index ddba9d0e..faa3a423 100644
--- a/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp
+++ b/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp
@@ -63,6 +63,9 @@ public:
void disconnect();
bool isConnected() const;
+ bool waitForRead(const int msecs = 30000);
+ bool waitForWrite(const int msecs = 30000);
+
void receive(string& buffer);
size_t receiveRaw(byte_t* buffer, const size_t count);
@@ -100,8 +103,6 @@ private:
byte_t m_buffer[65536];
- bool m_handshaking;
-
exception* m_ex;
unsigned int m_status;
diff --git a/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp b/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp
index 595a0091..bec41612 100644
--- a/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp
+++ b/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp
@@ -113,7 +113,7 @@ void TLSSocket_OpenSSL::createSSLHandle()
SSL_set_bio(m_ssl, sockBio, sockBio);
SSL_set_connect_state(m_ssl);
- SSL_set_mode(m_ssl, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+ SSL_set_mode(m_ssl, SSL_MODE_AUTO_RETRY | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
}
else
{
@@ -193,6 +193,18 @@ shared_ptr <timeoutHandler> TLSSocket_OpenSSL::getTimeoutHandler()
}
+bool TLSSocket_OpenSSL::waitForRead(const int msecs)
+{
+ return m_wrapped->waitForRead(msecs);
+}
+
+
+bool TLSSocket_OpenSSL::waitForWrite(const int msecs)
+{
+ return m_wrapped->waitForWrite(msecs);
+}
+
+
void TLSSocket_OpenSSL::receive(string& buffer)
{
const size_t size = receiveRaw(m_buffer, sizeof(m_buffer));
@@ -221,7 +233,7 @@ size_t TLSSocket_OpenSSL::receiveRaw(byte_t* buffer, const size_t count)
if (!m_ssl)
throw exceptions::socket_not_connected_exception();
- m_status &= ~STATUS_WOULDBLOCK;
+ m_status &= ~(STATUS_WANT_WRITE | STATUS_WANT_READ);
int rc = SSL_read(m_ssl, buffer, static_cast <int>(count));
@@ -232,9 +244,14 @@ size_t TLSSocket_OpenSSL::receiveRaw(byte_t* buffer, const size_t count)
{
int error = SSL_get_error(m_ssl, rc);
- if (error == SSL_ERROR_WANT_WRITE || error == SSL_ERROR_WANT_READ)
+ if (error == SSL_ERROR_WANT_WRITE)
+ {
+ m_status |= STATUS_WANT_WRITE;
+ return 0;
+ }
+ else if (error == SSL_ERROR_WANT_READ)
{
- m_status |= STATUS_WOULDBLOCK;
+ m_status |= STATUS_WANT_READ;
return 0;
}
@@ -250,7 +267,7 @@ void TLSSocket_OpenSSL::sendRaw(const byte_t* buffer, const size_t count)
if (!m_ssl)
throw exceptions::socket_not_connected_exception();
- m_status &= ~STATUS_WOULDBLOCK;
+ m_status &= ~(STATUS_WANT_WRITE | STATUS_WANT_READ);
for (size_t size = count ; size > 0 ; )
{
@@ -260,9 +277,14 @@ void TLSSocket_OpenSSL::sendRaw(const byte_t* buffer, const size_t count)
{
int error = SSL_get_error(m_ssl, rc);
- if (error == SSL_ERROR_WANT_WRITE || error == SSL_ERROR_WANT_READ)
+ if (error == SSL_ERROR_WANT_READ)
+ {
+ m_wrapped->waitForRead();
+ continue;
+ }
+ else if (error == SSL_ERROR_WANT_WRITE)
{
- platform::getHandler()->wait();
+ m_wrapped->waitForWrite();
continue;
}
@@ -282,7 +304,7 @@ size_t TLSSocket_OpenSSL::sendRawNonBlocking(const byte_t* buffer, const size_t
if (!m_ssl)
throw exceptions::socket_not_connected_exception();
- m_status &= ~STATUS_WOULDBLOCK;
+ m_status &= ~(STATUS_WANT_WRITE | STATUS_WANT_READ);
int rc = SSL_write(m_ssl, buffer, static_cast <int>(count));
@@ -293,9 +315,14 @@ size_t TLSSocket_OpenSSL::sendRawNonBlocking(const byte_t* buffer, const size_t
{
int error = SSL_get_error(m_ssl, rc);
- if (error == SSL_ERROR_WANT_WRITE || error == SSL_ERROR_WANT_READ)
+ if (error == SSL_ERROR_WANT_WRITE)
{
- m_status |= STATUS_WOULDBLOCK;
+ m_status |= STATUS_WANT_WRITE;
+ return 0;
+ }
+ else if (error == SSL_ERROR_WANT_READ)
+ {
+ m_status |= STATUS_WANT_READ;
return 0;
}
@@ -328,15 +355,12 @@ void TLSSocket_OpenSSL::handshake()
{
const int err = SSL_get_error(m_ssl, rc);
- if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)
- {
- // No data available yet
- platform::getHandler()->wait();
- }
- else
- {
- handleError(rc);
- }
+ if (err == SSL_ERROR_WANT_READ)
+ m_wrapped->waitForRead();
+ else if (err == SSL_ERROR_WANT_WRITE)
+ m_wrapped->waitForWrite();
+ else
+ handleError(rc);
// Check whether the time-out delay is elapsed
if (toHandler && toHandler->isTimeOut())
diff --git a/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp b/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp
index 5fbed19d..20712263 100644
--- a/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp
+++ b/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp
@@ -67,6 +67,9 @@ public:
void disconnect();
bool isConnected() const;
+ bool waitForRead(const int msecs = 30000);
+ bool waitForWrite(const int msecs = 30000);
+
void receive(string& buffer);
size_t receiveRaw(byte_t* buffer, const size_t count);
@@ -115,7 +118,7 @@ private:
unsigned long m_status;
// Last exception thrown from C BIO functions
- std::auto_ptr <std::exception> m_ex;
+ std::auto_ptr <exception> m_ex;
};
diff --git a/src/vmime/platform.hpp b/src/vmime/platform.hpp
index c72f160e..ac1834ac 100644
--- a/src/vmime/platform.hpp
+++ b/src/vmime/platform.hpp
@@ -104,13 +104,6 @@ public:
*/
virtual const charset getLocalCharset() const = 0;
- /** This function is called when VMime library is waiting for
- * something (for example, it is called when there is no data
- * available in a socket). On POSIX-compliant systems, a
- * simple call to sched_yield() should suffice.
- */
- virtual void wait() const = 0;
-
#if VMIME_HAVE_MESSAGING_FEATURES
/** Return a pointer to the default socket factory for
* this platform.
diff --git a/src/vmime/platforms/posix/posixHandler.cpp b/src/vmime/platforms/posix/posixHandler.cpp
index e0bfd27f..f23f09ad 100644
--- a/src/vmime/platforms/posix/posixHandler.cpp
+++ b/src/vmime/platforms/posix/posixHandler.cpp
@@ -269,24 +269,6 @@ shared_ptr <vmime::utility::childProcessFactory> posixHandler::getChildProcessFa
#endif
-void posixHandler::wait() const
-{
-/*
-#ifdef _POSIX_PRIORITY_SCHEDULING
- ::sched_yield();
-#else
- ::sleep(1);
-#endif // _POSIX_PRIORITY_SCHEDULING
-*/
-
- struct timespec ts;
- ts.tv_sec = 0;
- ts.tv_nsec = 500000; // 500 microseconds
-
- nanosleep(&ts, NULL);
-}
-
-
void posixHandler::generateRandomBytes(unsigned char* buffer, const unsigned int count)
{
int fd = open("/dev/urandom", O_RDONLY);
diff --git a/src/vmime/platforms/posix/posixHandler.hpp b/src/vmime/platforms/posix/posixHandler.hpp
index 0bba372b..d9eb4fbd 100644
--- a/src/vmime/platforms/posix/posixHandler.hpp
+++ b/src/vmime/platforms/posix/posixHandler.hpp
@@ -76,8 +76,6 @@ public:
shared_ptr <vmime::utility::childProcessFactory> getChildProcessFactory();
#endif
- void wait() const;
-
void generateRandomBytes(unsigned char* buffer, const unsigned int count);
shared_ptr <utility::sync::criticalSection> createCriticalSection();
diff --git a/src/vmime/platforms/posix/posixSocket.cpp b/src/vmime/platforms/posix/posixSocket.cpp
index e7eba9f1..4bb709e6 100644
--- a/src/vmime/platforms/posix/posixSocket.cpp
+++ b/src/vmime/platforms/posix/posixSocket.cpp
@@ -34,6 +34,7 @@
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
+#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>
@@ -111,13 +112,23 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port
// Connect to host
int sock = -1;
struct ::addrinfo* res = res0;
+ int connectErrno = 0;
- for ( ; sock == -1 && res != NULL ; res = res->ai_next)
+ if (m_timeoutHandler != NULL)
+ m_timeoutHandler->resetTimeOut();
+
+ for ( ; sock == -1 && res != NULL ; res = res->ai_next, connectErrno = ETIMEDOUT)
{
+ if (res->ai_family != AF_INET && res->ai_family != AF_INET6)
+ continue;
+
sock = ::socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sock < 0)
+ {
+ connectErrno = errno;
continue; // try next
+ }
if (m_timeoutHandler != NULL)
{
@@ -142,37 +153,51 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port
default:
+ connectErrno = errno;
::close(sock);
sock = -1;
continue; // try next
}
// Wait for socket to be connected.
- // We will check for time out every second.
- fd_set fds;
- FD_ZERO(&fds);
- FD_SET(sock, &fds);
-
- fd_set fdsError;
- FD_ZERO(&fdsError);
- FD_SET(sock, &fdsError);
+ bool connected = false;
- struct timeval tm;
- tm.tv_sec = 1;
- tm.tv_usec = 0;
+ const int selectTimeout = 1000; // select() timeout (ms)
+ const int tryNextTimeout = 5000; // maximum time before trying next (ms)
- m_timeoutHandler->resetTimeOut();
-
- bool connected = false;
+ timeval startTime = { 0, 0 };
+ gettimeofday(&startTime, /* timezone */ NULL);
do
{
- const int ret = select(sock + 1, NULL, &fds, &fdsError, &tm);
+ struct timeval tm;
+ tm.tv_sec = selectTimeout / 1000;
+ tm.tv_usec = selectTimeout % 1000;
+
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(sock, &fds);
+
+ const int ret = select(sock + 1, NULL, &fds, NULL, &tm);
// Success
if (ret > 0)
{
- connected = true;
+ int error = 0;
+ socklen_t len = sizeof(error);
+
+ if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
+ {
+ connectErrno = errno;
+ }
+ else
+ {
+ if (error != 0)
+ connectErrno = error;
+ else
+ connected = true;
+ }
+
break;
}
// Error
@@ -181,10 +206,11 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port
if (errno != EINTR)
{
// Cancel connection
+ connectErrno = errno;
break;
}
}
- // 1-second timeout
+ // Check for timeout
else if (ret == 0)
{
if (m_timeoutHandler->isTimeOut())
@@ -192,6 +218,7 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port
if (!m_timeoutHandler->handleTimeOut())
{
// Cancel connection
+ connectErrno = ETIMEDOUT;
break;
}
else
@@ -206,7 +233,15 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port
}
}
- ::sched_yield();
+ timeval curTime = { 0, 0 };
+ gettimeofday(&curTime, /* timezone */ NULL);
+
+ if (res->ai_next != NULL &&
+ curTime.tv_usec - startTime.tv_usec >= tryNextTimeout * 1000)
+ {
+ connectErrno = ETIMEDOUT;
+ break;
+ }
} while (true);
@@ -219,11 +254,17 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port
break;
}
+ else
+ {
+ // Connection successful
+ break;
+ }
}
else
{
if (::connect(sock, res->ai_addr, res->ai_addrlen) < 0)
{
+ connectErrno = errno;
::close(sock);
sock = -1;
continue; // try next
@@ -237,7 +278,7 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port
{
try
{
- throwSocketError(errno);
+ throwSocketError(connectErrno);
}
catch (exceptions::socket_exception& e)
{
@@ -434,6 +475,65 @@ size_t posixSocket::getBlockSize() const
}
+bool posixSocket::waitForData(const bool read, const bool write, const int msecs)
+{
+ for (int i = 0 ; i <= msecs / 10 ; ++i)
+ {
+ // Check whether data is available
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(m_desc, &fds);
+
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 10000; // 10 ms
+
+ ssize_t ret = ::select(m_desc + 1, read ? &fds : NULL, write ? &fds : NULL, NULL, &tv);
+
+ if (ret <= 0)
+ {
+ if (ret < 0 && !IS_EAGAIN(errno))
+ throwSocketError(errno);
+
+ // No data available at this time
+ // Check if we are timed out
+ if (m_timeoutHandler &&
+ m_timeoutHandler->isTimeOut())
+ {
+ if (!m_timeoutHandler->handleTimeOut())
+ {
+ // Server did not react within timeout delay
+ throw exceptions::operation_timed_out();
+ }
+ else
+ {
+ // Reset timeout
+ m_timeoutHandler->resetTimeOut();
+ }
+ }
+ }
+ else if (ret > 0)
+ {
+ return true;
+ }
+ }
+
+ return false; // time out
+}
+
+
+bool posixSocket::waitForRead(const int msecs)
+{
+ return waitForData(/* read */ true, /* write */ false, msecs);
+}
+
+
+bool posixSocket::waitForWrite(const int msecs)
+{
+ return waitForData(/* read */ false, /* write */ true, msecs);
+}
+
+
void posixSocket::receive(vmime::string& buffer)
{
const size_t size = receiveRaw(m_buffer, sizeof(m_buffer));
@@ -446,38 +546,8 @@ size_t posixSocket::receiveRaw(byte_t* buffer, const size_t count)
m_status &= ~STATUS_WOULDBLOCK;
// Check whether data is available
- fd_set fds;
- FD_ZERO(&fds);
- FD_SET(m_desc, &fds);
-
- struct timeval tv;
- tv.tv_sec = 1;
- tv.tv_usec = 0;
-
- ssize_t ret = ::select(m_desc + 1, &fds, NULL, NULL, &tv);
-
- if (ret < 0)
+ if (!waitForRead(50 /* msecs */))
{
- if (!IS_EAGAIN(errno))
- throwSocketError(errno);
-
- // No data available at this time
- // Check if we are timed out
- if (m_timeoutHandler &&
- m_timeoutHandler->isTimeOut())
- {
- if (!m_timeoutHandler->handleTimeOut())
- {
- // Server did not react within timeout delay
- throwSocketError(errno);
- }
- else
- {
- // Reset timeout
- m_timeoutHandler->resetTimeOut();
- }
- }
-
m_status |= STATUS_WOULDBLOCK;
// Continue waiting for data
@@ -485,7 +555,7 @@ size_t posixSocket::receiveRaw(byte_t* buffer, const size_t count)
}
// Read available data
- ret = ::recv(m_desc, buffer, count, 0);
+ ssize_t ret = ::recv(m_desc, buffer, count, 0);
if (ret < 0)
{
@@ -556,7 +626,7 @@ void posixSocket::sendRaw(const byte_t* buffer, const size_t count)
if (ret < 0 && !IS_EAGAIN(errno))
throwSocketError(errno);
- platform::getHandler()->wait();
+ waitForWrite(50 /* msecs */);
}
else
{
@@ -589,7 +659,7 @@ size_t posixSocket::sendRawNonBlocking(const byte_t* buffer, const size_t count)
if (!m_timeoutHandler->handleTimeOut())
{
// Could not send data within timeout delay
- throwSocketError(errno);
+ throw exceptions::operation_timed_out();
}
else
{
diff --git a/src/vmime/platforms/posix/posixSocket.hpp b/src/vmime/platforms/posix/posixSocket.hpp
index 5d29d710..b8009154 100644
--- a/src/vmime/platforms/posix/posixSocket.hpp
+++ b/src/vmime/platforms/posix/posixSocket.hpp
@@ -50,6 +50,9 @@ public:
bool isConnected() const;
void disconnect();
+ bool waitForRead(const int msecs = 30000);
+ bool waitForWrite(const int msecs = 30000);
+
void receive(vmime::string& buffer);
size_t receiveRaw(byte_t* buffer, const size_t count);
@@ -69,6 +72,8 @@ public:
protected:
+ bool waitForData(const bool read, const bool write, const int msecs);
+
static void throwSocketError(const int err);
private:
diff --git a/src/vmime/platforms/windows/windowsHandler.cpp b/src/vmime/platforms/windows/windowsHandler.cpp
index 9c96b271..4bc32dae 100644
--- a/src/vmime/platforms/windows/windowsHandler.cpp
+++ b/src/vmime/platforms/windows/windowsHandler.cpp
@@ -299,12 +299,6 @@ shared_ptr <vmime::utility::childProcessFactory> windowsHandler::getChildProcess
#endif
-void windowsHandler::wait() const
-{
- ::Sleep(100);
-}
-
-
void windowsHandler::generateRandomBytes(unsigned char* buffer, const unsigned int count)
{
HCRYPTPROV cryptProvider = 0;
diff --git a/src/vmime/platforms/windows/windowsHandler.hpp b/src/vmime/platforms/windows/windowsHandler.hpp
index 4a3678eb..b80f0de8 100644
--- a/src/vmime/platforms/windows/windowsHandler.hpp
+++ b/src/vmime/platforms/windows/windowsHandler.hpp
@@ -75,8 +75,6 @@ public:
shared_ptr <vmime::utility::childProcessFactory> getChildProcessFactory();
#endif
- void wait() const;
-
void generateRandomBytes(unsigned char* buffer, const unsigned int count);
shared_ptr <utility::sync::criticalSection> createCriticalSection();
diff --git a/src/vmime/platforms/windows/windowsSocket.cpp b/src/vmime/platforms/windows/windowsSocket.cpp
index bd20e5d4..d18fa1a1 100644
--- a/src/vmime/platforms/windows/windowsSocket.cpp
+++ b/src/vmime/platforms/windows/windowsSocket.cpp
@@ -249,10 +249,7 @@ size_t windowsSocket::receiveRaw(byte_t* buffer, const size_t count)
m_status &= ~STATUS_WOULDBLOCK;
// Check whether data is available
- bool timedout;
- waitForData(READ, timedout);
-
- if (timedout)
+ if (!waitForRead(50 /* msecs */))
{
// No data available at this time
// Check if we are timed out
@@ -335,8 +332,7 @@ void windowsSocket::sendRaw(const byte_t* buffer, const size_t count)
if (err != WSAEWOULDBLOCK)
throwSocketError(err);
- bool timedout;
- waitForData(WRITE, timedout);
+ waitForWrite(50 /* msecs */);
}
else
{
@@ -430,33 +426,62 @@ void windowsSocket::throwSocketError(const int err)
}
-void windowsSocket::waitForData(const WaitOpType t, bool& timedOut)
+bool windowsSocket::waitForData(const bool read, const bool write, const int msecs)
{
- // Check whether data is available
- fd_set fds;
- FD_ZERO(&fds);
- FD_SET(m_desc, &fds);
-
- struct timeval tv;
- tv.tv_sec = 1;
- tv.tv_usec = 0;
+ for (int i = 0 ; i <= msecs / 10 ; ++i)
+ {
+ // Check whether data is available
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(m_desc, &fds);
- int ret;
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 10000; // 10 ms
- if (t & READ)
- ret = ::select(m_desc + 1, &fds, NULL, NULL, &tv);
- else if (t & WRITE)
- ret = ::select(m_desc + 1, NULL, &fds, NULL, &tv);
- else
- ret = ::select(m_desc + 1, &fds, &fds, NULL, &tv);
+ ssize_t ret = ::select(m_desc + 1, read ? &fds : NULL, write ? &fds : NULL, NULL, &tv);
- timedOut = (ret == 0);
+ if (ret == SOCKET_ERROR)
+ {
+ int err = WSAGetLastError();
+ throwSocketError(err);
+ }
+ else if (ret > 0)
+ {
+ return true;
+ }
- if (ret == SOCKET_ERROR)
- {
- int err = WSAGetLastError();
- throwSocketError(err);
+ // No data available at this time
+ // Check if we are timed out
+ if (m_timeoutHandler &&
+ m_timeoutHandler->isTimeOut())
+ {
+ if (!m_timeoutHandler->handleTimeOut())
+ {
+ // Server did not react within timeout delay
+ throw exceptions::operation_timed_out();
+ }
+ else
+ {
+ // Reset timeout
+ m_timeoutHandler->resetTimeOut();
+ }
+ }
}
+
+ return false; // time out
+}
+
+
+bool windowsSocket::waitForRead(const int msecs)
+{
+ return waitForData(/* read */ true, /* write */ false, msecs);
+}
+
+
+bool windowsSocket::waitForWrite(const int msecs = 30000)
+{
+ return waitForData(/* read */ false, /* write */ true, msecs);
}
diff --git a/src/vmime/platforms/windows/windowsSocket.hpp b/src/vmime/platforms/windows/windowsSocket.hpp
index 31e1488b..42ba3156 100644
--- a/src/vmime/platforms/windows/windowsSocket.hpp
+++ b/src/vmime/platforms/windows/windowsSocket.hpp
@@ -54,6 +54,9 @@ public:
bool isConnected() const;
void disconnect();
+ bool waitForRead(const int msecs = 30000);
+ bool waitForWrite(const int msecs = 30000);
+
void receive(vmime::string& buffer);
size_t receiveRaw(byte_t* buffer, const size_t count);
@@ -75,14 +78,7 @@ protected:
void throwSocketError(const int err);
- enum WaitOpType
- {
- READ = 1,
- WRITE = 2,
- BOTH = 4
- };
-
- void waitForData(const WaitOpType t, bool& timedOut);
+ bool waitForData(const bool read, const bool write, const int msecs);
private:
diff --git a/src/vmime/security/sasl/SASLSocket.cpp b/src/vmime/security/sasl/SASLSocket.cpp
index 541fc904..de468fb3 100644
--- a/src/vmime/security/sasl/SASLSocket.cpp
+++ b/src/vmime/security/sasl/SASLSocket.cpp
@@ -102,6 +102,18 @@ shared_ptr <net::timeoutHandler> SASLSocket::getTimeoutHandler()
}
+bool SASLSocket::waitForRead(const int msecs)
+{
+ return m_wrapped->waitForRead(msecs);
+}
+
+
+bool SASLSocket::waitForWrite(const int msecs)
+{
+ return m_wrapped->waitForWrite(msecs);
+}
+
+
void SASLSocket::receive(string& buffer)
{
const size_t n = receiveRaw(m_recvBuffer, sizeof(m_recvBuffer));
diff --git a/src/vmime/security/sasl/SASLSocket.hpp b/src/vmime/security/sasl/SASLSocket.hpp
index d2d82411..474d5596 100644
--- a/src/vmime/security/sasl/SASLSocket.hpp
+++ b/src/vmime/security/sasl/SASLSocket.hpp
@@ -58,6 +58,9 @@ public:
bool isConnected() const;
+ bool waitForRead(const int msecs = 30000);
+ bool waitForWrite(const int msecs = 30000);
+
void receive(string& buffer);
size_t receiveRaw(byte_t* buffer, const size_t count);
diff --git a/tests/testUtils.cpp b/tests/testUtils.cpp
index ee642bea..c4fce447 100644
--- a/tests/testUtils.cpp
+++ b/tests/testUtils.cpp
@@ -85,6 +85,18 @@ vmime::shared_ptr <vmime::net::timeoutHandler> testSocket::getTimeoutHandler()
}
+bool testSocket::waitForRead(const int msecs)
+{
+ return true;
+}
+
+
+bool testSocket::waitForWrite(const int msecs)
+{
+ return true;
+}
+
+
void testSocket::receive(vmime::string& buffer)
{
buffer = m_inBuffer;
diff --git a/tests/testUtils.hpp b/tests/testUtils.hpp
index 9e72158a..20ebdf20 100644
--- a/tests/testUtils.hpp
+++ b/tests/testUtils.hpp
@@ -248,6 +248,9 @@ public:
bool isConnected() const;
+ bool waitForWrite(const int msecs = 30000);
+ bool waitForRead(const int msecs = 30000);
+
void receive(vmime::string& buffer);
void send(const vmime::string& buffer);
void send(const char* str);