@@ -141,14 +141,15 @@ private static CallSite callSite(final CallSite[] sites, final CallSiteIndex ind
141141 return sites [ index .ordinal () ];
142142 }
143143
144- private SSLContext sslContext ;
144+ private static final ByteBuffer EMPTY_DATA = ByteBuffer .allocate (0 ).asReadOnlyBuffer ();
145+
146+ SSLContext sslContext ;
145147 private SSLEngine engine ;
146148 private RubyIO io ;
147149
148- private ByteBuffer appReadData ;
149- private ByteBuffer netReadData ;
150- private ByteBuffer netWriteData ;
151- private final ByteBuffer dummy = ByteBuffer .allocate (0 ); // could be static
150+ ByteBuffer appReadData ;
151+ ByteBuffer netReadData ;
152+ ByteBuffer netWriteData ;
152153
153154 private boolean initialHandshake = false ;
154155 private transient long initializeTime ;
@@ -209,7 +210,7 @@ private IRubyObject fallback_set_io_nonblock_checked(ThreadContext context, Ruby
209210
210211 private static final String SESSION_SOCKET_ID = "socket_id" ;
211212
212- private SSLEngine ossl_ssl_setup (final ThreadContext context , final boolean server ) {
213+ SSLEngine ossl_ssl_setup (final ThreadContext context , final boolean server ) {
213214 SSLEngine engine = this .engine ;
214215 if ( engine != null ) return engine ;
215216
@@ -553,10 +554,6 @@ private static void writeWouldBlock(final Ruby runtime, final boolean exception,
553554 result [0 ] = WRITE_WOULD_BLOCK_RESULT ;
554555 }
555556
556- private void doHandshake (final boolean blocking ) throws IOException {
557- doHandshake (blocking , true );
558- }
559-
560557 // might return :wait_readable | :wait_writable in case (true, false)
561558 private IRubyObject doHandshake (final boolean blocking , final boolean exception ) throws IOException {
562559 while (true ) {
@@ -578,7 +575,7 @@ private IRubyObject doHandshake(final boolean blocking, final boolean exception)
578575 doTasks ();
579576 break ;
580577 case NEED_UNWRAP :
581- if (readAndUnwrap (blocking ) == -1 && handshakeStatus != SSLEngineResult .HandshakeStatus .FINISHED ) {
578+ if (readAndUnwrap (blocking , exception ) == -1 && handshakeStatus != SSLEngineResult .HandshakeStatus .FINISHED ) {
582579 throw new SSLHandshakeException ("Socket closed" );
583580 }
584581 // during initialHandshake, calling readAndUnwrap that results UNDERFLOW does not mean writable.
@@ -614,7 +611,7 @@ private IRubyObject doHandshake(final boolean blocking, final boolean exception)
614611
615612 private void doWrap (final boolean blocking ) throws IOException {
616613 netWriteData .clear ();
617- SSLEngineResult result = engine .wrap (dummy , netWriteData );
614+ SSLEngineResult result = engine .wrap (EMPTY_DATA . duplicate () , netWriteData );
618615 netWriteData .flip ();
619616 handshakeStatus = result .getHandshakeStatus ();
620617 status = result .getStatus ();
@@ -689,7 +686,9 @@ public int write(ByteBuffer src, boolean blocking) throws SSLException, IOExcept
689686 if ( netWriteData .hasRemaining () ) {
690687 flushData (blocking );
691688 }
692- netWriteData .clear ();
689+ // compact() to preserve encrypted bytes flushData could not send (non-blocking partial write)
690+ // clear() would discard them, corrupting the TLS record stream:
691+ netWriteData .compact ();
693692 final SSLEngineResult result = engine .wrap (src , netWriteData );
694693 if ( result .getStatus () == SSLEngineResult .Status .CLOSED ) {
695694 throw getRuntime ().newIOError ("closed SSL engine" );
@@ -704,11 +703,15 @@ public int write(ByteBuffer src, boolean blocking) throws SSLException, IOExcept
704703 }
705704
706705 public int read (final ByteBuffer dst , final boolean blocking ) throws IOException {
706+ return read (dst , blocking , true );
707+ }
708+
709+ private int read (final ByteBuffer dst , final boolean blocking , final boolean exception ) throws IOException {
707710 if ( initialHandshake ) return 0 ;
708711 if ( engine .isInboundDone () ) return -1 ;
709712
710713 if ( ! appReadData .hasRemaining () ) {
711- int appBytesProduced = readAndUnwrap (blocking );
714+ int appBytesProduced = readAndUnwrap (blocking , exception );
712715 if (appBytesProduced == -1 || appBytesProduced == 0 ) {
713716 return appBytesProduced ;
714717 }
@@ -719,17 +722,16 @@ public int read(final ByteBuffer dst, final boolean blocking) throws IOException
719722 return limit ;
720723 }
721724
722- private int readAndUnwrap (final boolean blocking ) throws IOException {
725+ private int readAndUnwrap (final boolean blocking , final boolean exception ) throws IOException {
723726 final int bytesRead = socketChannelImpl ().read (netReadData );
724727 if ( bytesRead == -1 ) {
725728 if ( ! netReadData .hasRemaining () ||
726729 ( status == SSLEngineResult .Status .BUFFER_UNDERFLOW ) ) {
727730 closeInbound ();
728731 return -1 ;
729732 }
730- // inbound channel has been already closed but closeInbound() must
731- // be defered till the last engine.unwrap() call.
732- // peerNetData could not be empty.
733+ // inbound channel has been already closed but closeInbound() must be defered till
734+ // the last engine.unwrap() call; peerNetData could not be empty
733735 }
734736 appReadData .clear ();
735737 netReadData .flip ();
@@ -768,7 +770,7 @@ private int readAndUnwrap(final boolean blocking) throws IOException {
768770 handshakeStatus == SSLEngineResult .HandshakeStatus .NEED_TASK ||
769771 handshakeStatus == SSLEngineResult .HandshakeStatus .NEED_WRAP ||
770772 handshakeStatus == SSLEngineResult .HandshakeStatus .FINISHED ) ) {
771- doHandshake (blocking );
773+ doHandshake (blocking , exception );
772774 }
773775 return appReadData .remaining ();
774776 }
@@ -793,7 +795,7 @@ private void doShutdown() throws IOException {
793795 }
794796 netWriteData .clear ();
795797 try {
796- engine .wrap (dummy , netWriteData ); // send close (after sslEngine.closeOutbound)
798+ engine .wrap (EMPTY_DATA . duplicate () , netWriteData ); // send close (after sslEngine.closeOutbound)
797799 }
798800 catch (SSLException e ) {
799801 debug (getRuntime (), "SSLSocket.doShutdown" , e );
@@ -808,10 +810,10 @@ private void doShutdown() throws IOException {
808810 }
809811
810812 /**
811- * @return the ( @link RubyString} buffer or :wait_readable / :wait_writeable {@link RubySymbol}
813+ * @return the { @link RubyString} buffer or :wait_readable / :wait_writeable {@link RubySymbol}
812814 */
813- private IRubyObject sysreadImpl (final ThreadContext context , final IRubyObject len , final IRubyObject buff ,
814- final boolean blocking , final boolean exception ) {
815+ private IRubyObject sysreadImpl (final ThreadContext context ,
816+ final IRubyObject len , final IRubyObject buff , final boolean blocking , final boolean exception ) {
815817 final Ruby runtime = context .runtime ;
816818
817819 final int length = RubyNumeric .fix2int (len );
@@ -831,6 +833,14 @@ private IRubyObject sysreadImpl(final ThreadContext context, final IRubyObject l
831833 }
832834
833835 try {
836+ // Flush pending write data before reading (after write_nonblock encrypted bytes may still be buffered)
837+ if ( engine != null && netWriteData .hasRemaining () ) {
838+ if ( flushData (blocking ) && ! blocking ) {
839+ if ( exception ) throw newSSLErrorWaitWritable (runtime , "write would block" );
840+ return runtime .newSymbol ("wait_writable" );
841+ }
842+ }
843+
834844 // So we need to make sure to only block when there is no data left to process
835845 if ( engine == null || ! ( appReadData .hasRemaining () || netReadData .position () > 0 ) ) {
836846 final Object ex = waitSelect (SelectionKey .OP_READ , blocking , exception );
@@ -839,12 +849,12 @@ private IRubyObject sysreadImpl(final ThreadContext context, final IRubyObject l
839849
840850 final ByteBuffer dst = ByteBuffer .allocate (length );
841851 int read = -1 ;
842- // ensure >0 bytes read; sysread is blocking read.
852+ // ensure > 0 bytes read; sysread is blocking read
843853 while ( read <= 0 ) {
844854 if ( engine == null ) {
845855 read = socketChannelImpl ().read (dst );
846856 } else {
847- read = read (dst , blocking );
857+ read = read (dst , blocking , exception );
848858 }
849859
850860 if ( read == -1 ) {
@@ -1226,7 +1236,7 @@ public IRubyObject ssl_version(ThreadContext context) {
12261236 return context .runtime .newString ( engine .getSession ().getProtocol () );
12271237 }
12281238
1229- private transient SocketChannelImpl socketChannel ;
1239+ transient SocketChannelImpl socketChannel ;
12301240
12311241 private SocketChannelImpl socketChannelImpl () {
12321242 if ( socketChannel != null ) return socketChannel ;
@@ -1241,7 +1251,7 @@ private SocketChannelImpl socketChannelImpl() {
12411251 throw new IllegalStateException ("unknow channel impl: " + channel + " of type " + channel .getClass ().getName ());
12421252 }
12431253
1244- private interface SocketChannelImpl {
1254+ interface SocketChannelImpl {
12451255
12461256 boolean isOpen () ;
12471257
0 commit comments