I'm a bit confused by expected proper usage pattern of APIs such as SSL_connect(), SSL_write(), etc. I've read up on some other posts on SO and elsewhere, and those that I found are all centered around blocking or non-blocking sockets (i.e. where BIO is given a socket to use for underlying IO), and the return errors SSL_ERROR_WANT_READ and SSL_ERROR_WANT_WRITE from calls in such configurations are pretty clear how to handle.
However, I'm a bit puzzled as to what the proper handling would be when BIO is set up without underlying IO socket, and instead all IO is handled via memory buffers. (The reason for such a setup is because encrypted data stream is not immediately sent over a vanila socket, but rather may be enveloped over other protocols or delivery mechanisms, and cannot be written to some socket directly). E.g. the BIO is set up as
auto readBio = BIO_new(BIO_s_mem());
auto writeBio = BIO_new(BIO_s_mem());
auto ssl = SSL_new(...);
SSL_set_bio(ssl, readBio, writeBio);
My assumption - albeit it appears to be incorrect - that after making a call to say SSL_connect(), it would tell me when it's time to pick up its output from write buffer using BIO_read() call and deliver that buffer (by whatever custom underlying transport means) to the other end peer; and likewise when to feed it data from peer. In other words, something akin to:
while (true) {
auto ret = SSL_connect(ssl); // or SSL_read(), SSL_write(), SSL_shutdown() in other contexts...
if (ret <= 0) {
auto err = SSL_get_error(ssl, ret);
switch(err) {
case SSL_ERROR_WANT_READ:
auto buf = magicallyReadDataFromPeer();
BIO_write(buf, ...);
continue;
case SSL_ERROR_WANT_WRITE:
Buffer buf;
BIO_read(buf, ...);
magicallySendDataToPeer();
continue;
}
} else break;
}
But I'm noticing that the first call to SSL_connect() always results in SSL_EROR_WANT_READ with nothing sent to peer to actually initiate TLS handshake, and so it blocks indefinitely.
If after calling SSL_connect() I do flush the buffer by doing BIO_read() and sending it out, then things seem to proceed. Same seems for SSL_write() calls, but then it seems that if I always flush buffer after call, and then check for SSL_ERROR_WANT_WRITE, I'd be flushing the buffer twice (with second one probably being a no-op) and that seems nonsensical. It also seems strange that I should just always ignore SSL_ERROR_WANT_WRITE of every SSL_connect/accept/write/read/shutdown calls since I'd be flushing always after each call.
And so I'm puzzled about what's the proper and expected dance between SSL_connect/etc and BIO_read/write calls and their tying relationship of SSL_ERROR_WANT_* values, specifically when using mem buffer instead of socket or file descriptor for underlying IO.