AMQP-CPP/src/linux_tcp/sslconnected.h

397 lines
12 KiB
C
Raw Normal View History

2018-03-06 15:40:44 +08:00
/**
* SslConnected.h
*
* The actual tcp connection over SSL
*
* @copyright 2018 copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "tcpoutbuffer.h"
#include "tcpinbuffer.h"
#include "poll.h"
2018-03-06 15:40:44 +08:00
#include "sslwrapper.h"
#include "sslshutdown.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class SslConnected : public TcpExtState
2018-03-06 15:40:44 +08:00
{
private:
/**
* The SSL structure
* @var SslWrapper
*/
SslWrapper _ssl;
/**
* The outgoing buffer
* @var TcpBuffer
*/
TcpOutBuffer _out;
/**
* The incoming buffer
* @var TcpInBuffer
*/
TcpInBuffer _in;
/**
* Are we now busy with sending or receiving?
* @var enum
*/
2018-03-09 20:55:49 +08:00
enum State {
2018-03-06 15:40:44 +08:00
state_idle,
state_sending,
state_receiving
} _state;
/**
2018-03-08 19:11:45 +08:00
* Should we close the connection after we've finished all operations?
2018-03-06 15:40:44 +08:00
* @var bool
*/
bool _closed = false;
2018-03-08 19:11:45 +08:00
2018-03-06 15:40:44 +08:00
/**
* Cached reallocation instruction
* @var size_t
*/
size_t _reallocate = 0;
/**
* Proceed with the next operation after the previous operation was
* a success, possibly changing the filedescriptor-monitor
* @return TcpState*
*/
TcpState *proceed()
{
// if we still have an outgoing buffer we want to send out data
if (_out)
{
// let's wait until the socket becomes writable
_parent->onIdle(this, _socket, readable | writable);
2018-03-06 15:40:44 +08:00
}
else if (_closed)
{
2018-03-08 20:36:56 +08:00
// start the state that closes the connection
return new SslShutdown(this, std::move(_ssl));
2018-03-06 15:40:44 +08:00
}
else
{
// let's wait until the socket becomes readable
_parent->onIdle(this, _socket, readable);
2018-03-06 15:40:44 +08:00
}
// done
return this;
}
/**
2018-03-08 19:11:45 +08:00
* Method to repeat the previous call\
* @param monitor monitor to check if connection object still exists
2018-03-09 20:55:49 +08:00
* @param state the state that we were in
* @param result result of an earlier SSL_get_error call
2018-03-06 15:40:44 +08:00
* @return TcpState*
*/
2018-03-09 20:55:49 +08:00
TcpState *repeat(const Monitor &monitor, enum State state, int error)
2018-03-06 15:40:44 +08:00
{
// check the error
switch (error) {
case SSL_ERROR_WANT_READ:
2018-03-09 20:55:49 +08:00
// remember state
_state = state;
2018-03-06 15:40:44 +08:00
// the operation must be repeated when readable
_parent->onIdle(this, _socket, readable);
// allow chaining
return monitor.valid() ? this : nullptr;
2018-03-06 15:40:44 +08:00
case SSL_ERROR_WANT_WRITE:
2018-03-09 20:55:49 +08:00
// remember state
_state = state;
2018-03-06 15:40:44 +08:00
// wait until socket becomes writable again
_parent->onIdle(this, _socket, readable | writable);
// allow chaining
return monitor.valid() ? this : nullptr;
case SSL_ERROR_NONE:
// we're ready for the next instruction from userspace
_state = state_idle;
2018-03-09 20:55:49 +08:00
2018-03-08 19:11:45 +08:00
// turns out no error occured, an no action has to be rescheduled
_parent->onIdle(this, _socket, _out || _closed ? readable | writable : readable);
2018-03-08 19:11:45 +08:00
// allow chaining
return monitor.valid() ? this : nullptr;
2018-03-06 15:40:44 +08:00
default:
// report an error to user-space
_parent->onError(this, "ssl protocol error");
// ssl level error, we have to tear down the tcp connection
return monitor.valid() ? new TcpClosed(this) : nullptr;
2018-03-06 15:40:44 +08:00
}
}
/**
* Parse the received buffer
2018-03-08 19:11:45 +08:00
* @param monitor object to check the existance of the connection object
* @param size number of bytes available
2018-03-06 15:40:44 +08:00
* @return TcpState
*/
2018-03-08 19:11:45 +08:00
TcpState *parse(const Monitor &monitor, size_t size)
2018-03-06 15:40:44 +08:00
{
// we need a local copy of the buffer - because it is possible that "this"
// object gets destructed halfway through the call to the parse() method
TcpInBuffer buffer(std::move(_in));
// parse the buffer
auto processed = _parent->onReceived(this, buffer);
2018-03-09 20:55:49 +08:00
2018-03-06 15:40:44 +08:00
// "this" could be removed by now, check this
if (!monitor.valid()) return nullptr;
// shrink buffer
buffer.shrink(processed);
// restore the buffer as member
_in = std::move(buffer);
// do we have to reallocate?
2018-03-09 20:55:49 +08:00
if (!_reallocate) return this;
2018-03-08 19:11:45 +08:00
// reallocate the buffer
_in.reallocate(_reallocate);
2018-03-06 15:40:44 +08:00
// we can remove the reallocate instruction
_reallocate = 0;
// done
2018-03-09 20:55:49 +08:00
return this;
}
/**
* Check if the socket is readable
* @return bool
*/
bool isReadable() const
{
// object to poll a socket
Poll poll(_socket);
// wait until socket is readable, but do not block
return poll.readable(false);
}
/**
* Check if the socket is writable
* @return bool
*/
bool isWritable() const
{
// object to poll a socket
Poll poll(_socket);
// wait until socket is writable, but do not block
return poll.writable(false);
}
2018-03-09 20:55:49 +08:00
/**
* Perform a write operation
* @param monitor object to check the existance of the connection object
2018-03-09 20:55:49 +08:00
* @return TcpState*
*/
TcpState *write(const Monitor &monitor)
2018-03-09 20:55:49 +08:00
{
// assume default state
_state = state_idle;
// we are going to check for errors after the openssl operations, so we make
// sure that the error queue is currently completely empty
OpenSSL::ERR_clear_error();
// because the output buffer contains a lot of small buffers, we can do multiple
// operations till the buffer is empty (but only if the socket is not also
// readable, because then we want to read that data first instead of endless writes
do
{
// try to send more data from the outgoing buffer
auto result = _out.sendto(_ssl);
// we may have to repeat the operation on failure
if (result > 0) continue;
// check for error
auto error = OpenSSL::SSL_get_error(_ssl, result);
// the operation failed, we may have to repeat our call
return repeat(monitor, state_sending, error);
}
while (_out && !isReadable());
2018-03-09 20:55:49 +08:00
// proceed with the read operation or the event loop
return isReadable() ? receive(monitor) : proceed();
2018-03-09 20:55:49 +08:00
}
/**
* Perform a receive operation
* @param monitor object to check the existance of the connection object
2018-03-09 20:55:49 +08:00
* @return TcpState
*/
TcpState *receive(const Monitor &monitor)
2018-03-09 20:55:49 +08:00
{
// we are going to check for errors after the openssl operations, so we make
// sure that the error queue is currently completely empty
OpenSSL::ERR_clear_error();
2018-03-09 20:55:49 +08:00
// start a loop
do
{
// assume default state
_state = state_idle;
// read data from ssl into the buffer
auto result = _in.receivefrom(_ssl, _parent->expected());
2018-03-09 20:55:49 +08:00
// if this is a failure, we are going to repeat the operation
if (result <= 0) return repeat(monitor, state_receiving, OpenSSL::SSL_get_error(_ssl, result));
2018-03-09 20:55:49 +08:00
// go process the received data
auto *nextstate = parse(monitor, result);
// leap out if we moved to a different state
if (nextstate != this) return nextstate;
}
while (OpenSSL::SSL_pending(_ssl) > 0);
// proceed with the write operation or the event loop
return _out && isWritable() ? write(monitor) : proceed();
2018-03-06 15:40:44 +08:00
}
2018-03-09 20:55:49 +08:00
2018-03-06 15:40:44 +08:00
public:
/**
* Constructor
* @param state The previous state
2018-03-06 15:40:44 +08:00
* @param ssl The SSL structure
* @param buffer The buffer that was already built
*/
SslConnected(TcpExtState *state, SslWrapper &&ssl, TcpOutBuffer &&buffer) :
TcpExtState(state),
_ssl(std::move(ssl)),
2018-03-06 15:40:44 +08:00
_out(std::move(buffer)),
_in(4096),
_state(_out ? state_sending : state_idle)
{
// tell the handler to monitor the socket if there is an out
_parent->onIdle(this, _socket, _state == state_sending ? readable | writable : readable);
2018-03-08 20:36:56 +08:00
}
2018-03-06 15:40:44 +08:00
/**
* Destructor
2018-03-06 15:40:44 +08:00
*/
virtual ~SslConnected() noexcept = default;
2018-03-06 15:40:44 +08:00
/**
2018-04-02 04:34:15 +08:00
* Number of bytes in the outgoing buffer
* @return std::size_t
*/
2018-04-02 04:34:15 +08:00
virtual std::size_t queued() const override { return _out.size(); }
2018-03-06 15:40:44 +08:00
/**
2018-03-08 19:11:45 +08:00
* Process the filedescriptor in the object
* @param monitor Object that can be used to find out if connection object is still alive
2018-03-06 15:40:44 +08:00
* @param fd The filedescriptor that is active
* @param flags AMQP::readable and/or AMQP::writable
* @return New implementation object
*/
2018-03-08 19:11:45 +08:00
virtual TcpState *process(const Monitor &monitor, int fd, int flags) override
2018-03-06 15:40:44 +08:00
{
// the socket must be the one this connection writes to
if (fd != _socket) return this;
2018-03-09 20:55:49 +08:00
// if we were busy with a write operation, we have to repeat that
if (_state == state_sending) return write(monitor);
2018-03-09 20:55:49 +08:00
// same is true for read operations, they should also be repeated
if (_state == state_receiving) return receive(monitor);
2018-03-09 20:55:49 +08:00
// if the socket is readable, we are going to receive data
if (flags & readable) return receive(monitor);
2018-03-09 20:55:49 +08:00
// socket is not readable (so it must be writable), do we have data to write?
if (_out) return write(monitor);
2018-03-09 20:55:49 +08:00
// the only scenario in which we can end up here is the socket should be
// closed, but instead of moving to the shutdown-state right, we call proceed()
// because that function is a little more careful
return proceed();
2018-03-06 15:40:44 +08:00
}
/**
* Send data over the connection
* @param buffer buffer to send
* @param size size of the buffer
*/
2018-03-08 19:11:45 +08:00
virtual void send(const char *buffer, size_t size) override
2018-03-06 15:40:44 +08:00
{
// put the data in the outgoing buffer
_out.add(buffer, size);
2018-03-06 15:40:44 +08:00
// if we're already busy with sending or receiving, we first have to wait
// for that operation to complete before we can move on
if (_state != state_idle) return;
// let's wait until the socket becomes writable
_parent->onIdle(this, _socket, readable | writable);
2018-03-06 15:40:44 +08:00
}
/**
* Gracefully close the connection
* @return TcpState The next state
2018-03-08 19:11:45 +08:00
*/
virtual TcpState *close() override
{
// remember that the object is going to be closed
_closed = true;
2018-03-08 19:11:45 +08:00
// if the previous operation is still in progress we can wait for that
if (_state != state_idle) return this;
2018-03-08 19:11:45 +08:00
// the connection can be closed right now, move to the next state
return new SslShutdown(this, std::move(_ssl));
2018-03-08 19:11:45 +08:00
}
2018-03-06 15:40:44 +08:00
/**
* Install max-frame size
* @param heartbeat suggested heartbeat
2018-03-06 15:40:44 +08:00
*/
virtual void maxframe(size_t maxframe) override
2018-03-06 15:40:44 +08:00
{
// remember that we have to reallocate (_in member can not be accessed because it is moved away)
_reallocate = maxframe;
2018-03-06 15:40:44 +08:00
}
};
/**
* End of namespace
*/
}