51
51
import org .jruby .Ruby ;
52
52
import org .jruby .RubyArray ;
53
53
import org .jruby .RubyClass ;
54
+ import org .jruby .RubyHash ;
54
55
import org .jruby .RubyIO ;
55
56
import org .jruby .RubyModule ;
56
57
import org .jruby .RubyNumeric ;
@@ -356,9 +357,10 @@ public IRubyObject verify_result(final ThreadContext context) {
356
357
// SelectableChannel.configureBlocking(false) permanently instead of setting
357
358
// temporarily. SSLSocket requires wrapping IO to be selectable so it should
358
359
// be OK to set configureBlocking(false) permanently.
359
- private boolean waitSelect (final int operations , final boolean blocking ) throws IOException {
360
+ private Object waitSelect (final int operations , final boolean blocking , final boolean exception )
361
+ throws IOException {
360
362
final SocketChannelImpl channel = socketChannelImpl ();
361
- if ( ! channel .isSelectable () ) return true ;
363
+ if ( ! channel .isSelectable () ) return Boolean . TRUE ;
362
364
363
365
final Ruby runtime = getRuntime ();
364
366
final RubyThread thread = runtime .getCurrentContext ().getThread ();
@@ -381,16 +383,20 @@ public void run() throws InterruptedException {
381
383
if ( result [0 ] == 0 ) {
382
384
if ((operations & SelectionKey .OP_READ ) != 0 && (operations & SelectionKey .OP_WRITE ) != 0 ) {
383
385
if ( key .isReadable () ) {
384
- writeWouldBlock (runtime );
385
- } else if ( key .isWritable () ) {
386
- readWouldBlock (runtime );
387
- } else { //neither, pick one
388
- readWouldBlock (runtime );
386
+ writeWouldBlock (runtime , exception , result ); return ;
389
387
}
390
- } else if ((operations & SelectionKey .OP_READ ) != 0 ) {
391
- readWouldBlock (runtime );
392
- } else if ((operations & SelectionKey .OP_WRITE ) != 0 ) {
393
- writeWouldBlock (runtime );
388
+ //else if ( key.isWritable() ) {
389
+ // readWouldBlock(runtime, exception, result);
390
+ //}
391
+ else { //neither, pick one
392
+ readWouldBlock (runtime , exception , result ); return ;
393
+ }
394
+ }
395
+ else if ((operations & SelectionKey .OP_READ ) != 0 ) {
396
+ readWouldBlock (runtime , exception , result ); return ;
397
+ }
398
+ else if ((operations & SelectionKey .OP_WRITE ) != 0 ) {
399
+ writeWouldBlock (runtime , exception , result ); return ;
394
400
}
395
401
}
396
402
}
@@ -408,16 +414,21 @@ public void wakeup() {
408
414
}
409
415
});
410
416
411
- if ( result [0 ] >= 1 ) {
412
- Set <SelectionKey > keySet = selector .selectedKeys ();
413
- if ( keySet .iterator ().next () == key ) return true ;
417
+ switch ( result [0 ] ) {
418
+ case READ_WOULD_BLOCK_RESULT :
419
+ return runtime .newSymbol ("wait_readable" ); // exception: false
420
+ case WRITE_WOULD_BLOCK_RESULT :
421
+ return runtime .newSymbol ("wait_writable" ); // exception: false
422
+ case 0 : return Boolean .FALSE ;
423
+ default :
424
+ if ( result [0 ] >= 1 ) {
425
+ Set <SelectionKey > keySet = selector .selectedKeys ();
426
+ if ( keySet .iterator ().next () == key ) return Boolean .TRUE ;
427
+ }
428
+ return Boolean .FALSE ;
414
429
}
415
-
416
- return false ;
417
- }
418
- catch (InterruptedException ie ) {
419
- return false ;
420
430
}
431
+ catch (InterruptedException interrupt ) { return Boolean .FALSE ; }
421
432
finally {
422
433
// Note: I don't like ignoring these exceptions, but it's
423
434
// unclear how likely they are to happen or what damage we
@@ -452,17 +463,22 @@ public void wakeup() {
452
463
}
453
464
}
454
465
455
- private static void readWouldBlock (final Ruby runtime ) {
456
- throw newSSLErrorWaitReadable (runtime , "read would block" );
466
+ private static final int READ_WOULD_BLOCK_RESULT = Integer .MIN_VALUE + 1 ;
467
+ private static final int WRITE_WOULD_BLOCK_RESULT = Integer .MIN_VALUE + 2 ;
468
+
469
+ private static void readWouldBlock (final Ruby runtime , final boolean exception , final int [] result ) {
470
+ if ( exception ) throw newSSLErrorWaitReadable (runtime , "read would block" );
471
+ result [0 ] = READ_WOULD_BLOCK_RESULT ;
457
472
}
458
473
459
- private static void writeWouldBlock (final Ruby runtime ) {
460
- throw newSSLErrorWaitWritable (runtime , "write would block" );
474
+ private static void writeWouldBlock (final Ruby runtime , final boolean exception , final int [] result ) {
475
+ if ( exception ) throw newSSLErrorWaitWritable (runtime , "write would block" );
476
+ result [0 ] = WRITE_WOULD_BLOCK_RESULT ;
461
477
}
462
478
463
479
private void doHandshake (final boolean blocking ) throws IOException {
464
480
while (true ) {
465
- boolean ready = waitSelect (SelectionKey .OP_READ | SelectionKey .OP_WRITE , blocking ) ;
481
+ boolean ready = waitSelect (SelectionKey .OP_READ | SelectionKey .OP_WRITE , blocking , true ) == Boolean . TRUE ;
466
482
467
483
// if not blocking, raise EAGAIN
468
484
if ( ! blocking && ! ready ) {
@@ -487,7 +503,7 @@ private void doHandshake(final boolean blocking) throws IOException {
487
503
// does not mean writable. we explicitly wait for readable channel to avoid
488
504
// busy loop.
489
505
if (initialHandshake && status == SSLEngineResult .Status .BUFFER_UNDERFLOW ) {
490
- waitSelect (SelectionKey .OP_READ , blocking );
506
+ waitSelect (SelectionKey .OP_READ , blocking , true );
491
507
}
492
508
break ;
493
509
case NEED_WRAP :
@@ -669,33 +685,34 @@ private void doShutdown() throws IOException {
669
685
flushData (true );
670
686
}
671
687
672
- private RubyString sysreadImpl (final ThreadContext context ,
673
- final IRubyObject [] args , final boolean blocking ) {
688
+ private IRubyObject do_sysread (final ThreadContext context ,
689
+ IRubyObject len , IRubyObject buff , final boolean blocking , final boolean exception ) {
674
690
final Ruby runtime = context .runtime ;
675
691
676
- final int len = RubyNumeric .fix2int (args [ 0 ] );
677
- final RubyString buff ;
692
+ final int length = RubyNumeric .fix2int (len );
693
+ final RubyString buffStr ;
678
694
679
- if ( args . length == 2 && ! args [ 1 ] .isNil () ) {
680
- buff = args [ 1 ] .asString ();
695
+ if ( buff != null && ! buff .isNil () ) {
696
+ buffStr = buff .asString ();
681
697
} else {
682
- buff = runtime . newString ();
698
+ buffStr = RubyString . newEmptyString ( runtime ); // fine since we're setValue
683
699
}
684
- if ( len == 0 ) {
685
- buff .clear ();
686
- return buff ;
700
+ if ( length == 0 ) {
701
+ buffStr .clear ();
702
+ return buffStr ;
687
703
}
688
- if ( len < 0 ) {
704
+ if ( length < 0 ) {
689
705
throw runtime .newArgumentError ("negative string size (or size too big)" );
690
706
}
691
707
692
708
try {
693
709
// So we need to make sure to only block when there is no data left to process
694
710
if ( engine == null || ! ( peerAppData .hasRemaining () || peerNetData .position () > 0 ) ) {
695
- waitSelect (SelectionKey .OP_READ , blocking );
711
+ final Object ex = waitSelect (SelectionKey .OP_READ , blocking , exception );
712
+ if ( ex instanceof IRubyObject ) return (IRubyObject ) ex ; // :wait_readable
696
713
}
697
714
698
- ByteBuffer dst = ByteBuffer .allocate (len );
715
+ ByteBuffer dst = ByteBuffer .allocate (length );
699
716
int rr = -1 ;
700
717
// ensure >0 bytes read; sysread is blocking read.
701
718
while ( rr <= 0 ) {
@@ -712,46 +729,92 @@ private RubyString sysreadImpl(final ThreadContext context,
712
729
// instead of spinning until the rest comes in, call waitSelect to either block
713
730
// until the rest is available, or throw a "read would block" error if we are in
714
731
// non-blocking mode.
715
- waitSelect (SelectionKey .OP_READ , blocking );
732
+ final Object ex = waitSelect (SelectionKey .OP_READ , blocking , exception );
733
+ if ( ex instanceof IRubyObject ) return (IRubyObject ) ex ; // :wait_readable
716
734
}
717
735
}
718
736
byte [] bss = new byte [rr ];
719
737
dst .position (dst .position () - rr );
720
738
dst .get (bss );
721
- buff .setValue (new ByteList (bss , false ));
722
- return buff ;
739
+ buffStr .setValue (new ByteList (bss , false ));
740
+ return buffStr ;
723
741
}
724
742
catch (IOException ioe ) {
725
743
throw runtime .newIOError (ioe .getMessage ());
726
744
}
727
745
}
728
746
729
- @ JRubyMethod (rest = true , required = 1 , optional = 1 )
747
+ @ JRubyMethod
748
+ public IRubyObject sysread (ThreadContext context , IRubyObject len ) {
749
+ return do_sysread (context , len , null , true , true );
750
+ }
751
+
752
+ @ JRubyMethod
753
+ public IRubyObject sysread (ThreadContext context , IRubyObject len , IRubyObject buff ) {
754
+ return do_sysread (context , len , buff , true , true );
755
+ }
756
+
757
+ @ Deprecated // @JRubyMethod(rest = true, required = 1, optional = 1)
730
758
public IRubyObject sysread (ThreadContext context , IRubyObject [] args ) {
731
- return sysreadImpl (context , args , true );
759
+ switch ( args .length ) {
760
+ case 1 :
761
+ return sysread (context , args [0 ]);
762
+ case 2 :
763
+ return sysread (context , args [0 ], args [1 ]);
764
+ }
765
+ Arity .checkArgumentCount (context .runtime , args .length , 1 , 2 );
766
+ return null ; // won't happen as checkArgumentCount raises
767
+ }
768
+
769
+ @ JRubyMethod
770
+ public IRubyObject sysread_nonblock (ThreadContext context , IRubyObject len ) {
771
+ return do_sysread (context , len , null , false , true );
732
772
}
733
773
734
- @ JRubyMethod (rest = true , required = 1 , optional = 2 )
774
+ @ JRubyMethod
775
+ public IRubyObject sysread_nonblock (ThreadContext context , IRubyObject len , IRubyObject arg ) {
776
+ if ( arg instanceof RubyHash ) { // exception: false
777
+ return do_sysread (context , len , null , false , getExceptionOpt (context , arg ));
778
+ }
779
+ return do_sysread (context , len , arg , false , true ); // buffer arg
780
+ }
781
+
782
+ @ JRubyMethod
783
+ public IRubyObject sysread_nonblock (ThreadContext context , IRubyObject len , IRubyObject buff , IRubyObject opts ) {
784
+ return do_sysread (context , len , buff , false , getExceptionOpt (context , opts ));
785
+ }
786
+
787
+
788
+ @ Deprecated // @JRubyMethod(rest = true, required = 1, optional = 2)
735
789
public IRubyObject sysread_nonblock (ThreadContext context , IRubyObject [] args ) {
736
- // TODO: options for exception raising
737
- return sysreadImpl (context , args , false );
790
+ switch ( args .length ) {
791
+ case 1 :
792
+ return sysread_nonblock (context , args [0 ]);
793
+ case 2 :
794
+ return sysread_nonblock (context , args [0 ], args [1 ]);
795
+ case 3 :
796
+ return sysread_nonblock (context , args [0 ], args [1 ], args [2 ]);
797
+ }
798
+ Arity .checkArgumentCount (context .runtime , args .length , 1 , 3 );
799
+ return null ; // won't happen as checkArgumentCount raises
738
800
}
739
801
740
802
private IRubyObject do_syswrite (final ThreadContext context ,
741
- final IRubyObject arg , final boolean blocking ) {
803
+ final IRubyObject arg , final boolean blocking , final boolean exception ) {
742
804
final Ruby runtime = context .runtime ;
743
805
try {
744
806
checkClosed ();
745
807
746
- waitSelect (SelectionKey .OP_WRITE , blocking );
808
+ final Object ex = waitSelect (SelectionKey .OP_WRITE , blocking , exception );
809
+ if ( ex instanceof IRubyObject ) return (IRubyObject ) ex ; // :wait_writable
747
810
748
- ByteList bls = arg .asString ().getByteList ();
749
- ByteBuffer b1 = ByteBuffer .wrap (bls .getUnsafeBytes (), bls .getBegin (), bls .getRealSize ());
811
+ ByteList bytes = arg .asString ().getByteList ();
812
+ ByteBuffer buff = ByteBuffer .wrap (bytes .getUnsafeBytes (), bytes .getBegin (), bytes .getRealSize ());
750
813
final int written ;
751
814
if ( engine == null ) {
752
- written = writeToChannel (b1 , blocking );
815
+ written = writeToChannel (buff , blocking );
753
816
} else {
754
- written = write (b1 , blocking );
817
+ written = write (buff , blocking );
755
818
}
756
819
757
820
this .io .callMethod (context , "flush" );
@@ -765,18 +828,26 @@ private IRubyObject do_syswrite(final ThreadContext context,
765
828
766
829
@ JRubyMethod
767
830
public IRubyObject syswrite (ThreadContext context , IRubyObject arg ) {
768
- return do_syswrite (context , arg , true );
831
+ return do_syswrite (context , arg , true , true );
769
832
}
770
833
771
834
@ JRubyMethod
772
835
public IRubyObject syswrite_nonblock (ThreadContext context , IRubyObject arg ) {
773
- return do_syswrite (context , arg , false );
836
+ return do_syswrite (context , arg , false , true );
774
837
}
775
838
776
839
@ JRubyMethod
777
- public IRubyObject syswrite_nonblock (ThreadContext context , IRubyObject arg , IRubyObject options ) {
778
- // TODO: options for exception raising
779
- return do_syswrite (context , arg , false );
840
+ public IRubyObject syswrite_nonblock (ThreadContext context , IRubyObject arg , IRubyObject opts ) {
841
+ return do_syswrite (context , arg , false , getExceptionOpt (context , opts ));
842
+ }
843
+
844
+ private static boolean getExceptionOpt (final ThreadContext context , final IRubyObject opts ) {
845
+ if ( opts instanceof RubyHash ) { // exception: true
846
+ final Ruby runtime = context .runtime ;
847
+ IRubyObject exc = ((RubyHash ) opts ).op_aref (context , runtime .newSymbol ("exception" ));
848
+ return exc != runtime .getFalse ();
849
+ }
850
+ return true ;
780
851
}
781
852
782
853
private void checkClosed () {
@@ -887,6 +958,14 @@ public IRubyObject cipher() {
887
958
return getRuntime ().newString ( engine .getSession ().getCipherSuite () );
888
959
}
889
960
961
+ @ JRubyMethod
962
+ public IRubyObject npn_protocol () {
963
+ if ( engine == null ) return getRuntime ().getNil ();
964
+ // NOTE: maybe a time to use https://github.com/benmmurphy/ssl_npn
965
+ warn (getRuntime ().getCurrentContext (), "WARNING: SSLSocket#npn_protocol is not supported" );
966
+ return getRuntime ().getNil (); // throw new UnsupportedOperationException();
967
+ }
968
+
890
969
@ JRubyMethod
891
970
public IRubyObject state () {
892
971
warn (getRuntime ().getCurrentContext (), "WARNING: unimplemented method called: SSLSocket#state" );
@@ -905,7 +984,7 @@ public IRubyObject session_reused_p() {
905
984
return getRuntime ().getNil (); // throw new UnsupportedOperationException();
906
985
}
907
986
908
- javax .net .ssl .SSLSession getSession () {
987
+ final javax .net .ssl .SSLSession getSession () {
909
988
return engine == null ? null : engine .getSession ();
910
989
}
911
990
0 commit comments