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