in c/src/ssl/openssl.c [1302:1422]
static ssize_t process_output_ssl( pn_transport_t *transport, unsigned int layer, char *buffer, size_t max_len)
{
pni_ssl_t *ssl = transport->ssl;
if (!ssl) return PN_EOS;
if (ssl->ssl == NULL && init_ssl_socket(transport, ssl, NULL)) return PN_EOS;
ssize_t written = 0;
bool work_pending;
do {
work_pending = false;
ERR_clear_error();
// first, get any pending application output, if possible
if (!ssl->app_output_closed && ssl->out_count < ssl->out_size) {
ssize_t app_bytes = transport->io_layers[layer+1]->process_output(transport, layer+1, &ssl->outbuf[ssl->out_count], ssl->out_size - ssl->out_count);
if (app_bytes > 0) {
ssl->out_count += app_bytes;
work_pending = true;
ssl_log(transport, PN_LEVEL_TRACE, "Gathered %" PN_ZI " bytes from app to send to peer", app_bytes);
} else {
if (app_bytes < 0) {
ssl_log(transport, PN_LEVEL_TRACE, "Application layer closed its output, error=%d (%d bytes pending send)",
(int) app_bytes, (int) ssl->out_count);
ssl->app_output_closed = app_bytes;
}
}
}
// now push any pending app data into the socket
if (!ssl->ssl_closed) {
char *data = ssl->outbuf;
if (ssl->out_count > 0) {
int wrote = BIO_write( ssl->bio_ssl, data, ssl->out_count );
if (wrote > 0) {
data += wrote;
ssl->out_count -= wrote;
work_pending = true;
ssl_log( transport, PN_LEVEL_TRACE, "Wrote %d bytes from app to socket", wrote );
} else {
if (!BIO_should_retry(ssl->bio_ssl)) {
int reason = SSL_get_error( ssl->ssl, wrote );
switch (reason) {
case SSL_ERROR_ZERO_RETURN:
// SSL closed cleanly
ssl_log(transport, PN_LEVEL_TRACE, "SSL connection has closed");
start_ssl_shutdown(transport); // KAG: not sure - this may not be necessary
ssl->out_count = 0; // can no longer write to socket, so erase app output data
ssl->ssl_closed = true;
break;
default:
// unexpected error
return (ssize_t)ssl_failed(transport, reason);
}
} else {
if (BIO_should_read( ssl->bio_ssl )) {
ssl->read_blocked = true;
ssl_log(transport, PN_LEVEL_TRACE, "Detected read-blocked");
}
if (BIO_should_write( ssl->bio_ssl )) {
ssl->write_blocked = true;
ssl_log(transport, PN_LEVEL_TRACE, "Detected write-blocked");
}
}
}
}
if (ssl->out_count == 0) {
if (ssl->app_input_closed && ssl->app_output_closed) {
// application is done sending/receiving data, and all buffered output data has
// been written to the SSL socket
start_ssl_shutdown(transport);
}
} else if (data != ssl->outbuf) {
memmove( ssl->outbuf, data, ssl->out_count );
}
}
// read from the network bio as much as possible, filling the buffer
if (max_len) {
int available = BIO_read( ssl->bio_net_io, buffer, max_len );
if (available > 0) {
max_len -= available;
buffer += available;
written += available;
ssl->write_blocked = false;
work_pending = work_pending || max_len > 0;
ssl_log(transport, PN_LEVEL_TRACE, "Read %d bytes from BIO Layer", available );
} else if ( !ssl->handshake_ok && !ssl->ssl_closed ) {
// OpenSSL bug workaround 1.0.x -> unknown. Harmless in all versions.
// See PROTON-2643. SSL_do_handshake() prevents forgetting to refill the BIO.
ssl->handshake_ok = (SSL_do_handshake(ssl->ssl) == 1);
}
}
} while (work_pending);
//_log(ssl, "written=%d ssl_closed=%d in_count=%d app_input_closed=%d app_output_closed=%d bio_pend=%d",
// written, ssl->ssl_closed, ssl->in_count, ssl->app_input_closed, ssl->app_output_closed, BIO_pending(ssl->bio_net_io) );
// PROTON-82: close the output side as soon as we've sent the SSL close_notify.
// We're not requiring the response, as some implementations never reply.
// ----
// Once no more data is available "below" the SSL socket, tell the transport we are
// done.
//if (written == 0 && ssl->ssl_closed && BIO_pending(ssl->bio_net_io) == 0) {
// written = ssl->app_output_closed ? ssl->app_output_closed : PN_EOS;
//}
if (written == 0 && (SSL_get_shutdown(ssl->ssl) & SSL_SENT_SHUTDOWN) && BIO_pending(ssl->bio_net_io) == 0) {
written = ssl->app_output_closed ? ssl->app_output_closed : PN_EOS;
if (transport->io_layers[layer]==&ssl_input_closed_layer) {
transport->io_layers[layer] = &ssl_closed_layer;
} else {
transport->io_layers[layer] = &ssl_output_closed_layer;
}
}
ssl_log(transport, PN_LEVEL_TRACE, "process_output_ssl() returning %d", (int) written);
return written;
}