Using SSL sockets and non-SSL sockets simultaneously in Boost.Asio?

DSB picture DSB · Jan 18, 2011 · Viewed 9.7k times · Source

I'm in the process of converting a library to Boost.Asio (which has worked very well so far), but I've hit something of a stumbling block with regards to a design decision.

Boost.Asio provides support for SSL, but a boost::asio::ssl::stream<boost::asio::ip::tcp::socket> type must be used for the socket. My library has the option of connecting to SSL servers or connecting normally, so I've made a class with two sockets like this:

class client : public boost::enable_shared_from_this<client>
{
public:
    client(boost::asio::io_service & io_service, boost::asio::ssl::context & context) : socket_(io_service), secureSocket_(io_service, context) {}
private:
    boost::asio::ip::tcp::socket socket_;
    boost::asio::ssl::stream<boost::asio::ip::tcp::socket> secureSocket_;
};

And within there are a bunch of handlers that reference socket_. (For example, I have socket_.is_open() in several places, which would need to become secureSocket_.lowest_layer().is_open() for the other socket.)

Can anyone suggest the best way to go about this? I'd rather not create a separate class just for this purpose, because that would mean duplicating a lot of code.

Edit: I rephrased my original question because I misunderstood the purpose of an OpenSSL function.

Answer

Nicole picture Nicole · Jan 9, 2012

I'm rather late in answering this question, but I hope this will help others. Sam's answer contains the germ of an idea, but doesn't quit go far enough in my opinion.

The idea came about from the observation that asio wraps an SSL socket in a stream. All this solution does is that it wraps the non-SSL socket similarly.

The desired result of having a uniform external interface between SSL and non-SSL sockets is done with three classes. One, the base, effectively defines the interface:

class Socket {
public:
    virtual boost::asio::ip::tcp::socket &getSocketForAsio() = 0;

    static Socket* create(boost::asio::io_service& iIoService, boost::asio::ssl::context *ipSslContext) {
        // Obviously this has to be in a separate source file since it makes reference to subclasses
        if (ipSslContext == nullptr) {
            return new NonSslSocket(iIoService);
        }
       return new SslSocket(iIoService, *ipSslContext);
    }

    size_t _read(void *ipData, size_t iLength) {
        return boost::asio::read(getSocketForAsio(), boost::asio::buffer(ipData, iLength));
    }
    size_t _write(const void *ipData, size_t iLength) {
        return boost::asio::write(getSocketForAsio(), boost::asio::buffer(ipData, iLength));
    }
};

Two sub-classes wrap SSL and non-SSL sockets.

typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> SslSocket_t;
class SslSocket: public Socket, private SslSocket_t {
public:
    SslSocket(boost::asio::io_service& iIoService, boost::asio::ssl::context &iSslContext) :
        SslSocket_t(iIoService, iSslContext) {
    }

private:
    boost::asio::ip::tcp::socket &getSocketForAsio() {
        return next_layer();
    }
};

and

class NonSslSocket: public Socket, private Socket_t {
public:
    NonSslSocket(boost::asio::io_service& iIoService) :
            Socket_t(iIoService) {
    }

private:
    boost::asio::ip::tcp::socket &getSocketForAsio() {
        return next_layer();
    }
};

Every time you call an asio function use getSocketForAsio(), rather than pass a reference to the Socket object. For example:

boost::asio::async_read(pSocket->getSocketForAsio(),
            boost::asio::buffer(&buffer, sizeof(buffer)),
            boost::bind(&Connection::handleRead,
                    shared_from_this(),
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred));

Notice that the Socket is stored as pointer. I cannot think how else the polymorphism can be hidden.

The penalty (which I don't think great) is the extra level of indirection used to obtain non-SSL sockets.