From 34fdd018ed7f227c5805df02bf6f69e4325cf91e Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 27 Aug 2026 20:56:29 +0900 Subject: [PATCH] ssl: implement blocking SSLSocket methods in Ruby Let the extension only provide the most basic non-blocking form, SSLSocket#ssl_connect, #ssl_accept, #ssl_read, #ssl_write. Implement SSLSocket#connect, #accept, #sysread, and #syswrite in lib/openssl/ssl.rb on top of these primitives and #wait_{read,writ}able. Also, handle the keyword arguments for the public #*_nonblock methods in Ruby to simplify and to avoid the Hash allocation. This prepares for the upcoming changes to SSLSocket to add proper timeout support and socket closed state tracking. These would be easier to do in Ruby. No user-visible behavior change is intended with this patch. --- ext/openssl/ossl_ssl.c | 545 ++++++++++------------------------------- lib/openssl/ssl.rb | 183 ++++++++++++++ 2 files changed, 317 insertions(+), 411 deletions(-) diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 91aadf0b6..d1047a4c1 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -37,7 +37,7 @@ static VALUE eSSLErrorWaitReadable; static VALUE eSSLErrorWaitWritable; static ID id_call, ID_callback_state, id_npn_protocols_encoded, id_each; -static VALUE sym_exception, sym_wait_readable, sym_wait_writable; +static VALUE sym_wait_readable, sym_wait_writable; static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode, id_i_verify_depth, id_i_verify_callback, id_i_client_ca, @@ -1772,255 +1772,99 @@ errno_mapped(void) #endif } -static void -write_would_block(int nonblock) -{ - if (nonblock) - ossl_raise(eSSLErrorWaitWritable, "write would block"); -} - -static void -read_would_block(int nonblock) -{ - if (nonblock) - ossl_raise(eSSLErrorWaitReadable, "read would block"); -} - -static int -no_exception_p(VALUE opts) -{ - if (RB_TYPE_P(opts, T_HASH) && - rb_hash_lookup2(opts, sym_exception, Qundef) == Qfalse) - return 1; - return 0; -} - -// Provided by Ruby 3.2.0 and later in order to support the default IO#timeout. -#ifndef RUBY_IO_TIMEOUT_DEFAULT -#define RUBY_IO_TIMEOUT_DEFAULT Qnil -#endif - -#ifdef HAVE_RB_IO_TIMEOUT -#define IO_TIMEOUT_ERROR rb_eIOTimeoutError -#else -#define IO_TIMEOUT_ERROR rb_eIOError -#endif - - -static void -io_wait_writable(VALUE io) -{ -#ifdef HAVE_RB_IO_MAYBE_WAIT - if (!rb_io_wait(io, INT2NUM(RUBY_IO_WRITABLE), RUBY_IO_TIMEOUT_DEFAULT)) { - rb_raise(IO_TIMEOUT_ERROR, "Timed out while waiting to become writable!"); - } -#else - rb_io_t *fptr; - GetOpenFile(io, fptr); - rb_thread_fd_writable(fptr->fd); -#endif -} - -static void -io_wait_readable(VALUE io) -{ -#ifdef HAVE_RB_IO_MAYBE_WAIT - if (!rb_io_wait(io, INT2NUM(RUBY_IO_READABLE), RUBY_IO_TIMEOUT_DEFAULT)) { - rb_raise(IO_TIMEOUT_ERROR, "Timed out while waiting to become readable!"); - } -#else - rb_io_t *fptr; - GetOpenFile(io, fptr); - rb_thread_wait_fd(fptr->fd); -#endif -} - static VALUE -ossl_start_ssl(VALUE self, int (*func)(SSL *), const char *funcname, VALUE opts) +ossl_start_ssl(VALUE self, int (*func)(SSL *), const char *funcname) { SSL *ssl; VALUE cb_state; - int nonblock = opts != Qfalse; rb_ivar_set(self, ID_callback_state, Qnil); GetSSL(self, ssl); - VALUE io = rb_attr_get(self, id_i_io); - for (;;) { - int ret = func(ssl); - int saved_errno = errno_mapped(); +#ifdef __APPLE__ + retry:; +#endif + int ret = func(ssl); + int saved_errno = errno_mapped(); - cb_state = rb_attr_get(self, ID_callback_state); - if (!NIL_P(cb_state)) { - /* must cleanup OpenSSL error stack before re-raising */ - ossl_clear_error(); - rb_jump_tag(NUM2INT(cb_state)); - } + cb_state = rb_attr_get(self, ID_callback_state); + if (!NIL_P(cb_state)) { + /* must cleanup OpenSSL error stack before re-raising */ + ossl_clear_error(); + rb_jump_tag(NUM2INT(cb_state)); + } + + if (ret > 0) + return self; - if (ret > 0) - break; - - int code = SSL_get_error(ssl, ret); - switch (code) { - case SSL_ERROR_WANT_WRITE: - if (no_exception_p(opts)) { return sym_wait_writable; } - write_would_block(nonblock); - io_wait_writable(io); - continue; - case SSL_ERROR_WANT_READ: - if (no_exception_p(opts)) { return sym_wait_readable; } - read_would_block(nonblock); - io_wait_readable(io); - continue; - case SSL_ERROR_SYSCALL: + int code = SSL_get_error(ssl, ret); + switch (code) { + case SSL_ERROR_WANT_WRITE: + return sym_wait_writable; + case SSL_ERROR_WANT_READ: + return sym_wait_readable; + case SSL_ERROR_SYSCALL: #ifdef __APPLE__ - /* See ossl_ssl_write_internal() */ - if (saved_errno == EPROTOTYPE) - continue; + /* See ossl_ssl_write_internal() */ + if (saved_errno == EPROTOTYPE) + goto retry; #endif - if (saved_errno) - rb_exc_raise(rb_syserr_new(saved_errno, funcname)); - /* fallthrough */ - default: { - VALUE error_append = Qnil; + if (saved_errno) + rb_exc_raise(rb_syserr_new(saved_errno, funcname)); + /* fallthrough */ + default: { + VALUE error_append = Qnil; #if defined(SSL_R_CERTIFICATE_VERIFY_FAILED) - unsigned long err = ERR_peek_last_error(); - if (ERR_GET_LIB(err) == ERR_LIB_SSL && - ERR_GET_REASON(err) == SSL_R_CERTIFICATE_VERIFY_FAILED) { - const char *err_msg = ERR_reason_error_string(err), - *verify_msg = X509_verify_cert_error_string(SSL_get_verify_result(ssl)); - if (!err_msg) - err_msg = "(null)"; - if (!verify_msg) - verify_msg = "(null)"; - ossl_clear_error(); /* let ossl_raise() not append message */ - error_append = rb_sprintf(": %s (%s)", err_msg, verify_msg); - } -#endif - ossl_raise(eSSLError, - "%s%s returned=%d errno=%d peeraddr=%"PRIsVALUE" state=%s%"PRIsVALUE, - funcname, - code == SSL_ERROR_SYSCALL ? " SYSCALL" : "", - code, - saved_errno, - peeraddr_ip_str(io), - SSL_state_string_long(ssl), - error_append); + unsigned long err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) == ERR_LIB_SSL && + ERR_GET_REASON(err) == SSL_R_CERTIFICATE_VERIFY_FAILED) { + long verify_result = SSL_get_verify_result(ssl); + const char *err_msg = ERR_reason_error_string(err), + *verify_msg = X509_verify_cert_error_string(verify_result); + if (!err_msg) + err_msg = "(null)"; + if (!verify_msg) + verify_msg = "(null)"; + ossl_clear_error(); /* let ossl_raise() not append message */ + error_append = rb_sprintf(": %s (%s)", err_msg, verify_msg); } - } +#endif + VALUE io = rb_attr_get(self, id_i_io); + ossl_raise(eSSLError, + "%s%s returned=%d errno=%d peeraddr=%"PRIsVALUE" state=%s%"PRIsVALUE, + funcname, + code == SSL_ERROR_SYSCALL ? " SYSCALL" : "", + code, + saved_errno, + peeraddr_ip_str(io), + SSL_state_string_long(ssl), + error_append); + } } - - return self; } -/* - * call-seq: - * ssl.connect => self - * - * Initiates an SSL/TLS handshake with a server. - */ static VALUE ossl_ssl_connect(VALUE self) { ossl_ssl_setup(self); - - return ossl_start_ssl(self, SSL_connect, "SSL_connect", Qfalse); + return ossl_start_ssl(self, SSL_connect, "SSL_connect"); } -/* - * call-seq: - * ssl.connect_nonblock([options]) => self - * - * Initiates the SSL/TLS handshake as a client in non-blocking manner. - * - * # emulates blocking connect - * begin - * ssl.connect_nonblock - * rescue IO::WaitReadable - * IO.select([s2]) - * retry - * rescue IO::WaitWritable - * IO.select(nil, [s2]) - * retry - * end - * - * By specifying a keyword argument _exception_ to +false+, you can indicate - * that connect_nonblock should not raise an IO::WaitReadable or - * IO::WaitWritable exception, but return the symbol +:wait_readable+ or - * +:wait_writable+ instead. - */ -static VALUE -ossl_ssl_connect_nonblock(int argc, VALUE *argv, VALUE self) -{ - VALUE opts; - rb_scan_args(argc, argv, "0:", &opts); - - ossl_ssl_setup(self); - - return ossl_start_ssl(self, SSL_connect, "SSL_connect", opts); -} - -/* - * call-seq: - * ssl.accept => self - * - * Waits for a SSL/TLS client to initiate a handshake. - */ static VALUE ossl_ssl_accept(VALUE self) { ossl_ssl_setup(self); - - return ossl_start_ssl(self, SSL_accept, "SSL_accept", Qfalse); + return ossl_start_ssl(self, SSL_accept, "SSL_accept"); } -/* - * call-seq: - * ssl.accept_nonblock([options]) => self - * - * Initiates the SSL/TLS handshake as a server in non-blocking manner. - * - * # emulates blocking accept - * begin - * ssl.accept_nonblock - * rescue IO::WaitReadable - * IO.select([s2]) - * retry - * rescue IO::WaitWritable - * IO.select(nil, [s2]) - * retry - * end - * - * By specifying a keyword argument _exception_ to +false+, you can indicate - * that accept_nonblock should not raise an IO::WaitReadable or - * IO::WaitWritable exception, but return the symbol +:wait_readable+ or - * +:wait_writable+ instead. - */ static VALUE -ossl_ssl_accept_nonblock(int argc, VALUE *argv, VALUE self) -{ - VALUE opts; - - rb_scan_args(argc, argv, "0:", &opts); - ossl_ssl_setup(self); - - return ossl_start_ssl(self, SSL_accept, "SSL_accept", opts); -} - -static VALUE -ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) +ossl_ssl_read(VALUE self, VALUE len, VALUE str) { SSL *ssl; int ilen; - VALUE len, str, cb_state; - VALUE opts = Qnil; + VALUE cb_state; - if (nonblock) { - rb_scan_args(argc, argv, "11:", &len, &str, &opts); - } else { - rb_scan_args(argc, argv, "11", &len, &str); - } GetSSL(self, ssl); if (!ssl_started(ssl)) rb_raise(eSSLError, "SSL session is not started yet"); @@ -2041,224 +1885,109 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) return str; } - VALUE io = rb_attr_get(self, id_i_io); + rb_str_locktmp(str); + int nread = SSL_read(ssl, RSTRING_PTR(str), ilen); + rb_str_unlocktmp(str); + int saved_errno = errno_mapped(); - for (;;) { - rb_str_locktmp(str); - int nread = SSL_read(ssl, RSTRING_PTR(str), ilen); - int saved_errno = errno_mapped(); - rb_str_unlocktmp(str); - - cb_state = rb_attr_get(self, ID_callback_state); - if (!NIL_P(cb_state)) { - rb_ivar_set(self, ID_callback_state, Qnil); - ossl_clear_error(); - rb_jump_tag(NUM2INT(cb_state)); - } + cb_state = rb_attr_get(self, ID_callback_state); + if (!NIL_P(cb_state)) { + rb_ivar_set(self, ID_callback_state, Qnil); + ossl_clear_error(); + rb_jump_tag(NUM2INT(cb_state)); + } - switch (SSL_get_error(ssl, nread)) { - case SSL_ERROR_NONE: - rb_str_set_len(str, nread); - return str; - case SSL_ERROR_ZERO_RETURN: - if (no_exception_p(opts)) { return Qnil; } - rb_eof_error(); - case SSL_ERROR_WANT_WRITE: - if (nonblock) { - if (no_exception_p(opts)) { return sym_wait_writable; } - write_would_block(nonblock); - } - io_wait_writable(io); - break; - case SSL_ERROR_WANT_READ: - if (nonblock) { - if (no_exception_p(opts)) { return sym_wait_readable; } - read_would_block(nonblock); - } - io_wait_readable(io); - break; - case SSL_ERROR_SYSCALL: - if (!ERR_peek_error()) { - if (saved_errno) - rb_exc_raise(rb_syserr_new(saved_errno, "SSL_read")); - else { - /* - * The underlying BIO returned 0. This is actually a - * protocol error. But unfortunately, not all - * implementations cleanly shutdown the TLS connection - * but just shutdown/close the TCP connection. So report - * EOF for now... - */ - if (no_exception_p(opts)) { return Qnil; } - rb_eof_error(); - } + switch (SSL_get_error(ssl, nread)) { + case SSL_ERROR_NONE: + rb_str_set_len(str, nread); + return str; + case SSL_ERROR_ZERO_RETURN: + return Qnil; + case SSL_ERROR_WANT_WRITE: + return sym_wait_writable; + case SSL_ERROR_WANT_READ: + return sym_wait_readable; + case SSL_ERROR_SYSCALL: + if (!ERR_peek_error()) { + if (saved_errno) + rb_exc_raise(rb_syserr_new(saved_errno, "SSL_read")); + else { + /* + * The underlying BIO returned 0. This is actually a + * protocol error. But unfortunately, not all + * implementations cleanly shutdown the TLS connection + * but just shutdown/close the TCP connection. So report + * EOF for now... + */ + return Qnil; } - /* fall through */ - default: - ossl_raise(eSSLError, "SSL_read"); } - - // Ensure the buffer is not modified during io_wait_*able() - rb_str_modify(str); - if (rb_str_capacity(str) < (size_t)ilen) - rb_raise(eSSLError, "read buffer was modified"); + /* fall through */ + default: + ossl_raise(eSSLError, "SSL_read"); } } -/* - * call-seq: - * ssl.sysread(length) => string - * ssl.sysread(length, buffer) => buffer - * - * Reads _length_ bytes from the SSL connection. If a pre-allocated _buffer_ - * is provided the data will be written into it. - */ static VALUE -ossl_ssl_read(int argc, VALUE *argv, VALUE self) -{ - return ossl_ssl_read_internal(argc, argv, self, 0); -} - -/* - * call-seq: - * ssl.sysread_nonblock(length) => string - * ssl.sysread_nonblock(length, buffer) => buffer - * ssl.sysread_nonblock(length[, buffer [, opts]) => buffer - * - * A non-blocking version of #sysread. Raises an SSLError if reading would - * block. If "exception: false" is passed, this method returns a symbol of - * :wait_readable, :wait_writable, or nil, rather than raising an exception. - * - * Reads _length_ bytes from the SSL connection. If a pre-allocated _buffer_ - * is provided the data will be written into it. - */ -static VALUE -ossl_ssl_read_nonblock(int argc, VALUE *argv, VALUE self) -{ - return ossl_ssl_read_internal(argc, argv, self, 1); -} - -static VALUE -ossl_ssl_write_internal_safe(VALUE _args) +ossl_ssl_write(VALUE self, VALUE str) { - VALUE *args = (VALUE*)_args; - VALUE self = args[0]; - VALUE str = args[1]; - VALUE opts = args[2]; - SSL *ssl; - rb_io_t *fptr; - int num, nonblock = opts != Qfalse; VALUE cb_state; GetSSL(self, ssl); if (!ssl_started(ssl)) rb_raise(eSSLError, "SSL session is not started yet"); - VALUE io = rb_attr_get(self, id_i_io); - GetOpenFile(io, fptr); - + StringValue(str); /* SSL_write(3ssl) manpage states num == 0 is undefined */ - num = RSTRING_LENINT(str); + int num = RSTRING_LENINT(str); if (num == 0) return INT2FIX(0); - for (;;) { - int nwritten = SSL_write(ssl, RSTRING_PTR(str), num); - int saved_errno = errno_mapped(); - - cb_state = rb_attr_get(self, ID_callback_state); - if (!NIL_P(cb_state)) { - rb_ivar_set(self, ID_callback_state, Qnil); - ossl_clear_error(); - rb_jump_tag(NUM2INT(cb_state)); - } - - switch (SSL_get_error(ssl, nwritten)) { - case SSL_ERROR_NONE: - return INT2NUM(nwritten); - case SSL_ERROR_WANT_WRITE: - if (no_exception_p(opts)) { return sym_wait_writable; } - write_would_block(nonblock); - io_wait_writable(io); - continue; - case SSL_ERROR_WANT_READ: - if (no_exception_p(opts)) { return sym_wait_readable; } - read_would_block(nonblock); - io_wait_readable(io); - continue; - case SSL_ERROR_SYSCALL: #ifdef __APPLE__ - /* - * It appears that send syscall can return EPROTOTYPE if the - * socket is being torn down. Retry to get a proper errno to - * make the error handling in line with the socket library. - * [Bug #14713] https://bugs.ruby-lang.org/issues/14713 - */ - if (saved_errno == EPROTOTYPE) - continue; + retry:; #endif - if (saved_errno) - rb_exc_raise(rb_syserr_new(saved_errno, "SSL_write")); - /* fallthrough */ - default: - ossl_raise(eSSLError, "SSL_write"); - } - } -} - - -static VALUE -ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts) -{ - StringValue(str); int frozen = RB_OBJ_FROZEN(str); if (!frozen) { rb_str_locktmp(str); } - int state; - VALUE args[3] = {self, str, opts}; - VALUE result = rb_protect(ossl_ssl_write_internal_safe, (VALUE)args, &state); + int nwritten = SSL_write(ssl, RSTRING_PTR(str), num); if (!frozen) { rb_str_unlocktmp(str); } + int saved_errno = errno_mapped(); - if (state) { - rb_jump_tag(state); + cb_state = rb_attr_get(self, ID_callback_state); + if (!NIL_P(cb_state)) { + rb_ivar_set(self, ID_callback_state, Qnil); + ossl_clear_error(); + rb_jump_tag(NUM2INT(cb_state)); } - return result; -} -/* - * call-seq: - * ssl.syswrite(string) => Integer - * - * Writes _string_ to the SSL connection. - */ -static VALUE -ossl_ssl_write(VALUE self, VALUE str) -{ - return ossl_ssl_write_internal(self, str, Qfalse); -} - -/* - * call-seq: - * ssl.syswrite_nonblock(string) => Integer - * ssl.syswrite_nonblock(string, opts) => Integer - * - * Writes _string_ to the SSL connection in a non-blocking manner. Raises an - * SSLError if writing would block. If "exception: false" is passed, this - * method returns a symbol of :wait_readable or :wait_writable, rather than - * raising an exception. - */ -static VALUE -ossl_ssl_write_nonblock(int argc, VALUE *argv, VALUE self) -{ - VALUE str, opts; - - rb_scan_args(argc, argv, "1:", &str, &opts); - - return ossl_ssl_write_internal(self, str, opts); + switch (SSL_get_error(ssl, nwritten)) { + case SSL_ERROR_NONE: + return INT2NUM(nwritten); + case SSL_ERROR_WANT_WRITE: + return sym_wait_writable; + case SSL_ERROR_WANT_READ: + return sym_wait_readable; + case SSL_ERROR_SYSCALL: +#ifdef __APPLE__ + /* + * It appears that send syscall can return EPROTOTYPE if the + * socket is being torn down. Retry to get a proper errno to + * make the error handling in line with the socket library. + * [Bug #14713] https://bugs.ruby-lang.org/issues/14713 + */ + if (saved_errno == EPROTOTYPE) + goto retry; +#endif + if (saved_errno) + rb_exc_raise(rb_syserr_new(saved_errno, "SSL_write")); + /* fallthrough */ + default: + ossl_raise(eSSLError, "SSL_write"); + } } /* @@ -3182,14 +2911,10 @@ Init_ossl_ssl(void) rb_define_alloc_func(cSSLSocket, ossl_ssl_s_alloc); rb_define_method(cSSLSocket, "initialize", ossl_ssl_initialize, -1); rb_undef_method(cSSLSocket, "initialize_copy"); - rb_define_method(cSSLSocket, "connect", ossl_ssl_connect, 0); - rb_define_method(cSSLSocket, "connect_nonblock", ossl_ssl_connect_nonblock, -1); - rb_define_method(cSSLSocket, "accept", ossl_ssl_accept, 0); - rb_define_method(cSSLSocket, "accept_nonblock", ossl_ssl_accept_nonblock, -1); - rb_define_method(cSSLSocket, "sysread", ossl_ssl_read, -1); - rb_define_private_method(cSSLSocket, "sysread_nonblock", ossl_ssl_read_nonblock, -1); - rb_define_method(cSSLSocket, "syswrite", ossl_ssl_write, 1); - rb_define_private_method(cSSLSocket, "syswrite_nonblock", ossl_ssl_write_nonblock, -1); + rb_define_private_method(cSSLSocket, "ssl_connect", ossl_ssl_connect, 0); + rb_define_private_method(cSSLSocket, "ssl_accept", ossl_ssl_accept, 0); + rb_define_private_method(cSSLSocket, "ssl_read", ossl_ssl_read, 2); + rb_define_private_method(cSSLSocket, "ssl_write", ossl_ssl_write, 1); rb_define_private_method(cSSLSocket, "stop", ossl_ssl_stop, 0); rb_define_method(cSSLSocket, "cert", ossl_ssl_get_cert, 0); rb_define_method(cSSLSocket, "peer_cert", ossl_ssl_get_peer_cert, 0); @@ -3367,8 +3092,6 @@ Init_ossl_ssl(void) /* TLS 1.3 */ rb_define_const(mSSL, "TLS1_3_VERSION", INT2NUM(TLS1_3_VERSION)); - - sym_exception = ID2SYM(rb_intern_const("exception")); sym_wait_readable = ID2SYM(rb_intern_const("wait_readable")); sym_wait_writable = ID2SYM(rb_intern_const("wait_writable")); diff --git a/lib/openssl/ssl.rb b/lib/openssl/ssl.rb index 486bdf858..fa4c26c55 100644 --- a/lib/openssl/ssl.rb +++ b/lib/openssl/ssl.rb @@ -360,6 +360,189 @@ def sysclose io.close if sync_close end + # Ruby 3.2 + IO_TimeoutError = defined?(IO::TimeoutError) ? IO::TimeoutError : IOError + private_constant :IO_TimeoutError + + private def check_nonblock(ret) + case ret + when :wait_readable + raise SSLErrorWaitReadable, "read would block" + when :wait_writable + raise SSLErrorWaitWritable, "write would block" + when nil + raise EOFError, "end of file reached" + else + ret + end + end + + # :call-seq: + # ssl.connect -> self + # + # Initiates an SSL/TLS handshake with a server. + def connect + while true + case ret = ssl_connect + when :wait_readable + wait_readable or + raise IO_TimeoutError, "Timed out while waiting to become readable!" + when :wait_writable + wait_writable or + raise IO_TimeoutError, "Timed out while waiting to become writable!" + else + return ret + end + end + end + + # :call-seq: + # ssl.connect_nonblock -> self + # ssl.connect_nonblock(exception: false) -> self | :wait_readable | :wait_writable + # + # Initiates the SSL/TLS handshake as a client in non-blocking manner. + # + # # emulates blocking connect + # begin + # ssl.connect_nonblock + # rescue IO::WaitReadable + # IO.select([s2]) + # retry + # rescue IO::WaitWritable + # IO.select(nil, [s2]) + # retry + # end + # + # By specifying a keyword argument _exception_ to +false+, you can + # indicate that connect_nonblock should not raise an IO::WaitReadable or + # IO::WaitWritable exception, but return the symbol +:wait_readable+ or + # +:wait_writable+ instead. + def connect_nonblock(exception: true) + ret = ssl_connect + check_nonblock(ret) if exception + ret + end + + # :call-seq: + # ssl.accept -> self + # + # Waits for a SSL/TLS client to initiate a handshake. + def accept + while true + case ret = ssl_accept + when :wait_readable + wait_readable or + raise IO_TimeoutError, "Timed out while waiting to become readable!" + when :wait_writable + wait_writable or + raise IO_TimeoutError, "Timed out while waiting to become writable!" + else + return ret + end + end + end + + # :call-seq: + # ssl.accept_nonblock -> self + # ssl.accept_nonblock(exception: false) -> self | :wait_readable | :wait_writable + # + # Initiates the SSL/TLS handshake as a server in non-blocking manner. + # + # # emulates blocking accept + # begin + # ssl.accept_nonblock + # rescue IO::WaitReadable + # IO.select([s2]) + # retry + # rescue IO::WaitWritable + # IO.select(nil, [s2]) + # retry + # end + # + # By specifying a keyword argument _exception_ to +false+, you can + # indicate that accept_nonblock should not raise an IO::WaitReadable or + # IO::WaitWritable exception, but return the symbol +:wait_readable+ or + # +:wait_writable+ instead. + def accept_nonblock(exception: true) + ret = ssl_accept + check_nonblock(ret) if exception + ret + end + + # :call-seq: + # ssl.sysread(length) -> string + # ssl.sysread(length, buffer) -> buffer + # + # Reads _length_ bytes from the SSL connection. If a pre-allocated + # _buffer_ is provided the data will be written into it. + def sysread(length, buffer = nil) + while true + case ret = ssl_read(length, buffer) + when :wait_readable + wait_readable or + raise IO_TimeoutError, "Timed out while waiting to become readable!" + when :wait_writable + wait_writable or + raise IO_TimeoutError, "Timed out while waiting to become writable!" + when nil + raise EOFError, "end of file reached" + else + return ret + end + end + end + + # :call-seq: + # ssl.sysread_nonblock(length) -> string + # ssl.sysread_nonblock(length, buffer) -> buffer + # ssl.sysread_nonblock(length, buffer, exception: false) -> buffer | :wait_readable | :wait_writable | nil + # + # A non-blocking version of #sysread. Raises an SSLError if reading + # would block. If "exception: false" is passed, this method returns a + # symbol of :wait_readable, :wait_writable, or nil, rather than raising + # an exception. + # + # Reads _length_ bytes from the SSL connection. If a pre-allocated + # _buffer_ is provided the data will be written into it. + private def sysread_nonblock(length, buffer = nil, exception: true) + ret = ssl_read(length, buffer) + check_nonblock(ret) if exception + ret + end + + # :call-seq: + # ssl.syswrite(string) -> Integer + # + # Writes _string_ to the SSL connection. + def syswrite(string) + while true + case ret = ssl_write(string) + when :wait_readable + wait_readable or + raise IO_TimeoutError, "Timed out while waiting to become readable!" + when :wait_writable + wait_writable or + raise IO_TimeoutError, "Timed out while waiting to become writable!" + else + return ret + end + end + end + + # :call-seq: + # ssl.syswrite_nonblock(string) -> Integer + # ssl.syswrite_nonblock(string, exception: false) -> Integer | :wait_readable | :wait_writable + # + # Writes _string_ to the SSL connection in a non-blocking manner. Raises + # an SSLError if writing would block. If "exception: false" is passed, + # this method returns a symbol of :wait_readable or :wait_writable, + # rather than raising an exception. + private def syswrite_nonblock(string, exception: true) + ret = ssl_write(string) + check_nonblock(ret) if exception + ret + end + # call-seq: # ssl.post_connection_check(hostname) -> true #