blob: feb91ec65fa62f74d1702ac20976285994f03e1b [file] [log] [blame]
//
// ssl/detail/openssl_operation.hpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2005 Voipster / Indrek dot Juhani at voipster dot com
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_ASIO_SSL_DETAIL_OPENSSL_OPERATION_HPP
#define BOOST_ASIO_SSL_DETAIL_OPENSSL_OPERATION_HPP
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
#include <boost/asio/detail/config.hpp>
#include <boost/function.hpp>
#include <boost/assert.hpp>
#include <boost/bind.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/detail/socket_ops.hpp>
#include <boost/asio/placeholders.hpp>
#include <boost/asio/ssl/detail/openssl_types.hpp>
#include <boost/asio/strand.hpp>
#include <boost/system/system_error.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/detail/push_options.hpp>
namespace boost {
namespace asio {
namespace ssl {
namespace detail {
typedef boost::function<int (::SSL*)> ssl_primitive_func;
typedef boost::function<void (const boost::system::error_code&, int)>
user_handler_func;
// Network send_/recv buffer implementation
//
//
class net_buffer
{
static const int NET_BUF_SIZE = 16*1024 + 256; // SSL record size + spare
unsigned char buf_[NET_BUF_SIZE];
unsigned char* data_start_;
unsigned char* data_end_;
public:
net_buffer()
{
data_start_ = data_end_ = buf_;
}
unsigned char* get_unused_start() { return data_end_; }
unsigned char* get_data_start() { return data_start_; }
size_t get_unused_len() { return (NET_BUF_SIZE - (data_end_ - buf_)); }
size_t get_data_len() { return (data_end_ - data_start_); }
void data_added(size_t count)
{
data_end_ += count;
data_end_ = data_end_ > (buf_ + NET_BUF_SIZE)?
(buf_ + NET_BUF_SIZE):
data_end_;
}
void data_removed(size_t count)
{
data_start_ += count;
if (data_start_ >= data_end_) reset();
}
void reset() { data_start_ = buf_; data_end_ = buf_; }
bool has_data() { return (data_start_ < data_end_); }
}; // class net_buffer
//
// Operation class
//
//
template <typename Stream>
class openssl_operation
{
public:
// Constructor for asynchronous operations
openssl_operation(ssl_primitive_func primitive,
Stream& socket,
net_buffer& recv_buf,
SSL* session,
BIO* ssl_bio,
user_handler_func handler,
boost::asio::io_service::strand& strand
)
: primitive_(primitive)
, user_handler_(handler)
, strand_(&strand)
, recv_buf_(recv_buf)
, socket_(socket)
, ssl_bio_(ssl_bio)
, session_(session)
{
write_ = boost::bind(
&openssl_operation::do_async_write,
this, boost::arg<1>(), boost::arg<2>()
);
read_ = boost::bind(
&openssl_operation::do_async_read,
this
);
handler_= boost::bind(
&openssl_operation::async_user_handler,
this, boost::arg<1>(), boost::arg<2>()
);
}
// Constructor for synchronous operations
openssl_operation(ssl_primitive_func primitive,
Stream& socket,
net_buffer& recv_buf,
SSL* session,
BIO* ssl_bio)
: primitive_(primitive)
, strand_(0)
, recv_buf_(recv_buf)
, socket_(socket)
, ssl_bio_(ssl_bio)
, session_(session)
{
write_ = boost::bind(
&openssl_operation::do_sync_write,
this, boost::arg<1>(), boost::arg<2>()
);
read_ = boost::bind(
&openssl_operation::do_sync_read,
this
);
handler_ = boost::bind(
&openssl_operation::sync_user_handler,
this, boost::arg<1>(), boost::arg<2>()
);
}
// Start operation
// In case of asynchronous it returns 0, in sync mode returns success code
// or throws an error...
int start()
{
int rc = primitive_( session_ );
bool is_operation_done = (rc > 0);
// For connect/accept/shutdown, the operation
// is done, when return code is 1
// for write, it is done, when is retcode > 0
// for read, is is done when retcode > 0
int error_code = !is_operation_done ?
::SSL_get_error( session_, rc ) :
0;
int sys_error_code = ERR_get_error();
if (error_code == SSL_ERROR_SSL)
return handler_(boost::system::error_code(
sys_error_code, boost::asio::error::get_ssl_category()), rc);
bool is_read_needed = (error_code == SSL_ERROR_WANT_READ);
bool is_write_needed = (error_code == SSL_ERROR_WANT_WRITE ||
::BIO_ctrl_pending( ssl_bio_ ));
bool is_shut_down_received =
((::SSL_get_shutdown( session_ ) & SSL_RECEIVED_SHUTDOWN) ==
SSL_RECEIVED_SHUTDOWN);
bool is_shut_down_sent =
((::SSL_get_shutdown( session_ ) & SSL_SENT_SHUTDOWN) ==
SSL_SENT_SHUTDOWN);
if (is_shut_down_sent && is_shut_down_received
&& is_operation_done && !is_write_needed)
// SSL connection is shut down cleanly
return handler_(boost::system::error_code(), 1);
if (is_shut_down_received && !is_operation_done)
// Shutdown has been requested, while we were reading or writing...
// abort our action...
return handler_(boost::asio::error::shut_down, 0);
if (!is_operation_done && !is_read_needed && !is_write_needed
&& !is_shut_down_sent)
{
// The operation has failed... It is not completed and does
// not want network communication nor does want to send shutdown out...
if (error_code == SSL_ERROR_SYSCALL)
{
return handler_(boost::system::error_code(
sys_error_code, boost::asio::error::system_category), rc);
}
else
{
return handler_(boost::system::error_code(
sys_error_code, boost::asio::error::get_ssl_category()), rc);
}
}
if (!is_operation_done && !is_write_needed)
{
// We may have left over data that we can pass to SSL immediately
if (recv_buf_.get_data_len() > 0)
{
// Pass the buffered data to SSL
int written = ::BIO_write
(
ssl_bio_,
recv_buf_.get_data_start(),
recv_buf_.get_data_len()
);
if (written > 0)
{
recv_buf_.data_removed(written);
}
else if (written < 0)
{
if (!BIO_should_retry(ssl_bio_))
{
// Some serios error with BIO....
return handler_(boost::asio::error::no_recovery, 0);
}
}
return start();
}
else if (is_read_needed || (is_shut_down_sent && !is_shut_down_received))
{
return read_();
}
}
// Continue with operation, flush any SSL data out to network...
return write_(is_operation_done, rc);
}
// Private implementation
private:
typedef boost::function<int (const boost::system::error_code&, int)>
int_handler_func;
typedef boost::function<int (bool, int)> write_func;
typedef boost::function<int ()> read_func;
ssl_primitive_func primitive_;
user_handler_func user_handler_;
boost::asio::io_service::strand* strand_;
write_func write_;
read_func read_;
int_handler_func handler_;
net_buffer send_buf_; // buffers for network IO
// The recv buffer is owned by the stream, not the operation, since there can
// be left over bytes after passing the data up to the application, and these
// bytes need to be kept around for the next read operation issued by the
// application.
net_buffer& recv_buf_;
Stream& socket_;
BIO* ssl_bio_;
SSL* session_;
//
int sync_user_handler(const boost::system::error_code& error, int rc)
{
if (!error)
return rc;
throw boost::system::system_error(error);
}
int async_user_handler(boost::system::error_code error, int rc)
{
if (rc < 0)
{
if (!error)
error = boost::asio::error::no_recovery;
rc = 0;
}
user_handler_(error, rc);
return 0;
}
// Writes bytes asynchronously from SSL to NET
int do_async_write(bool is_operation_done, int rc)
{
int len = ::BIO_ctrl_pending( ssl_bio_ );
if ( len )
{
// There is something to write into net, do it...
len = (int)send_buf_.get_unused_len() > len?
len:
send_buf_.get_unused_len();
if (len == 0)
{
// In case our send buffer is full, we have just to wait until
// previous send to complete...
return 0;
}
// Read outgoing data from bio
len = ::BIO_read( ssl_bio_, send_buf_.get_unused_start(), len);
if (len > 0)
{
unsigned char *data_start = send_buf_.get_unused_start();
send_buf_.data_added(len);
BOOST_ASSERT(strand_);
boost::asio::async_write
(
socket_,
boost::asio::buffer(data_start, len),
strand_->wrap
(
boost::bind
(
&openssl_operation::async_write_handler,
this,
is_operation_done,
rc,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
)
);
return 0;
}
else if (!BIO_should_retry(ssl_bio_))
{
// Seems like fatal error
// reading from SSL BIO has failed...
handler_(boost::asio::error::no_recovery, 0);
return 0;
}
}
if (is_operation_done)
{
// Finish the operation, with success
handler_(boost::system::error_code(), rc);
return 0;
}
// OPeration is not done and writing to net has been made...
// start operation again
start();
return 0;
}
void async_write_handler(bool is_operation_done, int rc,
const boost::system::error_code& error, size_t bytes_sent)
{
if (!error)
{
// Remove data from send buffer
send_buf_.data_removed(bytes_sent);
if (is_operation_done)
handler_(boost::system::error_code(), rc);
else
// Since the operation was not completed, try it again...
start();
}
else
handler_(error, rc);
}
int do_async_read()
{
// Wait for new data
BOOST_ASSERT(strand_);
socket_.async_read_some
(
boost::asio::buffer(recv_buf_.get_unused_start(),
recv_buf_.get_unused_len()),
strand_->wrap
(
boost::bind
(
&openssl_operation::async_read_handler,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
)
);
return 0;
}
void async_read_handler(const boost::system::error_code& error,
size_t bytes_recvd)
{
if (!error)
{
recv_buf_.data_added(bytes_recvd);
// Pass the received data to SSL
int written = ::BIO_write
(
ssl_bio_,
recv_buf_.get_data_start(),
recv_buf_.get_data_len()
);
if (written > 0)
{
recv_buf_.data_removed(written);
}
else if (written < 0)
{
if (!BIO_should_retry(ssl_bio_))
{
// Some serios error with BIO....
handler_(boost::asio::error::no_recovery, 0);
return;
}
}
// and try the SSL primitive again
start();
}
else
{
// Error in network level...
// SSL can't continue either...
handler_(error, 0);
}
}
// Syncronous functions...
int do_sync_write(bool is_operation_done, int rc)
{
int len = ::BIO_ctrl_pending( ssl_bio_ );
if ( len )
{
// There is something to write into net, do it...
len = (int)send_buf_.get_unused_len() > len?
len:
send_buf_.get_unused_len();
// Read outgoing data from bio
len = ::BIO_read( ssl_bio_, send_buf_.get_unused_start(), len);
if (len > 0)
{
size_t sent_len = boost::asio::write(
socket_,
boost::asio::buffer(send_buf_.get_unused_start(), len)
);
send_buf_.data_added(len);
send_buf_.data_removed(sent_len);
}
else if (!BIO_should_retry(ssl_bio_))
{
// Seems like fatal error
// reading from SSL BIO has failed...
throw boost::system::system_error(boost::asio::error::no_recovery);
}
}
if (is_operation_done)
// Finish the operation, with success
return rc;
// Operation is not finished, start again.
return start();
}
int do_sync_read()
{
size_t len = socket_.read_some
(
boost::asio::buffer(recv_buf_.get_unused_start(),
recv_buf_.get_unused_len())
);
// Write data to ssl
recv_buf_.data_added(len);
// Pass the received data to SSL
int written = ::BIO_write
(
ssl_bio_,
recv_buf_.get_data_start(),
recv_buf_.get_data_len()
);
if (written > 0)
{
recv_buf_.data_removed(written);
}
else if (written < 0)
{
if (!BIO_should_retry(ssl_bio_))
{
// Some serios error with BIO....
throw boost::system::system_error(boost::asio::error::no_recovery);
}
}
// Try the operation again
return start();
}
}; // class openssl_operation
} // namespace detail
} // namespace ssl
} // namespace asio
} // namespace boost
#include <boost/asio/detail/pop_options.hpp>
#endif // BOOST_ASIO_SSL_DETAIL_OPENSSL_OPERATION_HPP