application can subsequently change its mind about performing a rapid shutdown
by making a subsequent call to SSL_shutdown_ex() without the flag set.
+=head2 Peer-Initiated Shutdown
+
+In some cases, an application may wish to wait for a shutdown initiated by the
+peer rather than triggered locally. To do this, call SSL_shutdown_ex() with
+I<SSL_SHUTDOWN_FLAG_WAIT_PEER> specified in I<flags>. In blocking mode, this
+waits until the peer initiates a shutdown or the connection otherwise becomes
+terminated for another reason. In nonblocking mode it exits immediately with
+either success or failure depending on whether a shutdown has occurred.
+
+If a locally initiated shutdown has already been triggered or the connection has
+started terminating for another reason, this flag has no effect.
+
+=head2 Nonblocking Mode
+
+SSL_shutdown() and SSL_shutdown_ex() block if the connection is configured in
+blocking mode. This may be overridden by specifying
+B<SSL_SHUTDOWN_FLAG_NO_BLOCK> in I<flags> when calling SSL_shutdown_ex(), which
+causes the call to operate as though in nonblocking mode.
+
=head1 RETURN VALUES
For both SSL_shutdown() and SSL_shutdown_ex() the following return values can occur:
|| qc_shutdown_flush_finished(qc);
}
+static int quic_shutdown_peer_wait(void *arg)
+{
+ QUIC_CONNECTION *qc = arg;
+ return ossl_quic_channel_is_term_any(qc->ch);
+}
+
QUIC_TAKES_LOCK
int ossl_quic_conn_shutdown(SSL *s, uint64_t flags,
const SSL_SHUTDOWN_EX_ARGS *args,
int ret;
QCTX ctx;
int stream_flush = ((flags & SSL_SHUTDOWN_FLAG_NO_STREAM_FLUSH) == 0);
+ int no_block = ((flags & SSL_SHUTDOWN_FLAG_NO_BLOCK) != 0);
+ int wait_peer = ((flags & SSL_SHUTDOWN_FLAG_WAIT_PEER) != 0);
if (!expect_quic(s, &ctx))
return -1;
}
/* Phase 1: Stream Flushing */
- if (stream_flush) {
+ if (!wait_peer && stream_flush) {
qc_shutdown_flush_init(ctx.qc);
if (!qc_shutdown_flush_finished(ctx.qc)) {
- if (qc_blocking_mode(ctx.qc)) {
+ if (!no_block && qc_blocking_mode(ctx.qc)) {
ret = block_until_pred(ctx.qc, quic_shutdown_flush_wait, ctx.qc, 0);
if (ret < 1) {
ret = 0;
}
/* Phase 2: Connection Closure */
+ if (wait_peer && !ossl_quic_channel_is_term_any(ctx.qc->ch)) {
+ if (!no_block && qc_blocking_mode(ctx.qc)) {
+ ret = block_until_pred(ctx.qc, quic_shutdown_peer_wait, ctx.qc, 0);
+ if (ret < 1) {
+ ret = 0;
+ goto err;
+ }
+ } else {
+ ossl_quic_reactor_tick(ossl_quic_channel_get_reactor(ctx.qc->ch), 0);
+ }
+
+ if (!ossl_quic_channel_is_term_any(ctx.qc->ch)) {
+ ret = 0; /* peer hasn't closed yet - still not done */
+ goto err;
+ }
+
+ /*
+ * We are at least terminating - go through the normal process of
+ * waiting until we are in the TERMINATED state.
+ */
+ }
+
+ /* Block mutation ops regardless of if we did stream flush. */
+ ctx.qc->shutting_down = 1;
+
+ /*
+ * This call is a no-op if we are already terminating, so it doesn't
+ * affect the wait_peer case.
+ */
ossl_quic_channel_local_close(ctx.qc->ch,
args != NULL ? args->quic_error_code : 0,
args != NULL ? args->quic_reason : NULL);
}
/* Phase 3: Terminating Wait Time */
- if (qc_blocking_mode(ctx.qc) && (flags & SSL_SHUTDOWN_FLAG_RAPID) == 0) {
+ if (!no_block && qc_blocking_mode(ctx.qc)
+ && (flags & SSL_SHUTDOWN_FLAG_RAPID) == 0) {
ret = block_until_pred(ctx.qc, quic_shutdown_wait, ctx.qc, 0);
if (ret < 1) {
ret = 0;