AMQP-CPP/src/linux_tcp/sslshutdown.h

247 lines
6.9 KiB
C++

/**
* SslShutdown.h
*
* Class that takes care of the final handshake to close a SSL connection
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Begin of namespace
*/
namespace AMQP {
/**
* Class definition
*/
class SslShutdown : public TcpState, private Watchable
{
private:
/**
* The SSL context
* @var SslWrapper
*/
SslWrapper _ssl;
/**
* Socket file descriptor
* @var int
*/
int _socket;
/**
* Have we already notified user space of connection end?
* @var bool
*/
bool _finalized;
/**
* Close the socket
* @return bool
*/
bool close()
{
// skip if already closed
if (_socket < 0) return false;
// we're no longer interested in events
_handler->monitor(_connection, _socket, 0);
// close the socket
::close(_socket);
// forget the socket
_socket = -1;
// done
return true;
}
/**
* Report an error
* @param monitor object to check if connection still exists
* @return TcpState*
*/
TcpState *reporterror(const Monitor &monitor)
{
// close the socket
close();
// if we have already told user space that connection is gone
if (_finalized) return new TcpClosed(this);
// object will be finalized now
_finalized = true;
// inform user space that the party is over
_handler->onError(_connection, "ssl shutdown error");
// go to the final state (if not yet disconnected)
return monitor.valid() ? new TcpClosed(this) : nullptr;
}
/**
* Proceed with the next operation after the previous operation was
* a success, possibly changing the filedescriptor-monitor
* @param monitor object to check if connection still exists
* @return TcpState*
*/
TcpState *proceed(const Monitor &monitor)
{
// close the socket
close();
// if we have already told user space that connection is gone
if (_finalized) return new TcpClosed(this);
// object will be finalized now
_finalized = true;
// inform user space that the party is over
_handler->onClosed(_connection);
// go to the final state (if not yet disconnected)
return monitor.valid() ? new TcpClosed(this) : nullptr;
}
/**
* Method to repeat the previous call
* @param monitor object to check if connection still exists
* @param result result of an earlier openssl operation
* @return TcpState*
*/
TcpState *repeat(const Monitor &monitor, int result)
{
// error was returned, so we must investigate what is going on
auto error = OpenSSL::SSL_get_error(_ssl, result);
// check the error
switch (error) {
case SSL_ERROR_WANT_READ:
// the operation must be repeated when readable
_handler->monitor(_connection, _socket, readable);
return this;
case SSL_ERROR_WANT_WRITE:
// wait until socket becomes writable again
_handler->monitor(_connection, _socket, readable | writable);
return this;
default:
// go to the final state (if not yet disconnected)
return reporterror(monitor);
}
}
public:
/**
* Constructor
* @param connection Parent TCP connection object
* @param socket The socket filedescriptor
* @param ssl The SSL structure
* @param finalized Is the user already notified of connection end (onError() has been called)
* @param handler User-supplied handler object
*/
SslShutdown(TcpConnection *connection, int socket, SslWrapper &&ssl, bool finalized, TcpHandler *handler) :
TcpState(connection, handler),
_ssl(std::move(ssl)),
_socket(socket),
_finalized(finalized)
{
// wait until the socket is accessible
_handler->monitor(_connection, _socket, readable | writable);
}
/**
* Destructor
*/
virtual ~SslShutdown() noexcept
{
// close the socket
close();
}
/**
* The filedescriptor of this connection
* @return int
*/
virtual int fileno() const override { return _socket; }
/**
* Process the filedescriptor in the object
* @param monitor Object to check if connection still exists
* @param fd The filedescriptor that is active
* @param flags AMQP::readable and/or AMQP::writable
* @return New implementation object
*/
virtual TcpState *process(const Monitor &monitor, int fd, int flags) override
{
// the socket must be the one this connection writes to
if (fd != _socket) return this;
// close the connection
auto result = OpenSSL::SSL_shutdown(_ssl);
// on result==0 we need an additional call
while (result == 0) result = OpenSSL::SSL_shutdown(_ssl);
// if this is a success, we can proceed with the event loop
if (result > 0) return proceed(monitor);
// the operation failed, we may have to repeat our call
else return repeat(monitor, result);
}
/**
* Flush the connection, sent all buffered data to the socket
* @param monitor Object to check if connection still exists
* @return TcpState new tcp state
*/
virtual TcpState *flush(const Monitor &monitor) override
{
// create an object to wait for the filedescriptor to becomes active
Wait wait(_socket);
// keep looping
while (true)
{
// close the connection
auto result = OpenSSL::SSL_shutdown(_ssl);
// on result==0 we need an additional call
while (result == 0) result = OpenSSL::SSL_shutdown(_ssl);
// if this is a success, we can proceed with the event loop
if (result > 0) return proceed(monitor);
// error was returned, so we must investigate what is going on
auto error = OpenSSL::SSL_get_error(_ssl, result);
// check the error
switch (error) {
// if openssl reports that socket readability or writability is needed,
// we wait for that until this situation is reached
case SSL_ERROR_WANT_READ: wait.readable(); break;
case SSL_ERROR_WANT_WRITE: wait.active(); break;
// something is wrong, we proceed to the next state
default: return reporterror(monitor);
}
}
}
};
/**
* End of namespace
*/
}