@@ -2700,7 +2700,7 @@ mod _ssl {
27002700
27012701 // Helper to call socket methods, bypassing any SSL wrapper
27022702 pub ( crate ) fn sock_recv ( & self , size : usize , vm : & VirtualMachine ) -> PyResult < PyObjectRef > {
2703- // In BIO mode, read from incoming BIO
2703+ // In BIO mode, read from incoming BIO (flags not supported)
27042704 if let Some ( ref bio) = self . incoming_bio {
27052705 let bio_obj: PyObjectRef = bio. clone ( ) . into ( ) ;
27062706 let read_method = bio_obj. get_attr ( "read" , vm) ?;
@@ -2711,7 +2711,7 @@ mod _ssl {
27112711 let socket_mod = vm. import ( "socket" , 0 ) ?;
27122712 let socket_class = socket_mod. get_attr ( "socket" , vm) ?;
27132713
2714- // Call socket.socket.recv(self.sock, size)
2714+ // Call socket.socket.recv(self.sock, size, flags )
27152715 let recv_method = socket_class. get_attr ( "recv" , vm) ?;
27162716 recv_method. call ( ( self . sock . clone ( ) , vm. ctx . new_int ( size) ) , vm)
27172717 }
@@ -3229,9 +3229,10 @@ mod _ssl {
32293229 } ;
32303230 }
32313231
3232- // Ensure handshake is done
3232+ // Ensure handshake is done - if not, complete it first
3233+ // This matches OpenSSL behavior where SSL_read() auto-completes handshake
32333234 if !* self . handshake_done . lock ( ) {
3234- return Err ( vm . new_value_error ( "Handshake not completed" ) ) ;
3235+ self . do_handshake ( vm ) ? ;
32353236 }
32363237
32373238 // Check if connection has been shut down
@@ -3272,7 +3273,7 @@ mod _ssl {
32723273 } ;
32733274
32743275 // Use compat layer for unified read logic with proper EOF handling
3275- // This matches CPython's SSL_read_ex() approach
3276+ // This matches SSL_read_ex() approach
32763277 let mut buf = vec ! [ 0u8 ; len] ;
32773278 let read_result = {
32783279 let mut conn_guard = self . connection . lock ( ) ;
@@ -3360,9 +3361,10 @@ mod _ssl {
33603361 return Ok ( 0 ) ;
33613362 }
33623363
3363- // Ensure handshake is done
3364+ // Ensure handshake is done - if not, complete it first
3365+ // This matches OpenSSL behavior where SSL_write() auto-completes handshake
33643366 if !* self . handshake_done . lock ( ) {
3365- return Err ( vm . new_value_error ( "Handshake not completed" ) ) ;
3367+ self . do_handshake ( vm ) ? ;
33663368 }
33673369
33683370 // Check if connection has been shut down
@@ -3420,21 +3422,57 @@ mod _ssl {
34203422 } ) ?;
34213423
34223424 if !buf. is_empty ( ) {
3423- let timed_out =
3424- self . sock_wait_for_io_impl ( SelectKind :: Write , vm) ?;
3425- if timed_out {
3426- return Err ( vm. new_os_error ( "Write operation timed out" ) ) ;
3427- }
3425+ // Check if this is a non-blocking socket
3426+ let timeout = self . get_socket_timeout ( vm) ?;
3427+ let is_non_blocking =
3428+ timeout. map ( |t| t. is_zero ( ) ) . unwrap_or ( false ) ;
3429+
3430+ // Send all bytes, handling partial sends
3431+ let mut sent_total = 0 ;
3432+ while sent_total < buf. len ( ) {
3433+ let timed_out =
3434+ self . sock_wait_for_io_impl ( SelectKind :: Write , vm) ?;
3435+ if timed_out {
3436+ return Err (
3437+ vm. new_os_error ( "Write operation timed out" )
3438+ ) ;
3439+ }
34283440
3429- match self . sock_send ( buf, vm) {
3430- Ok ( _) => { }
3431- Err ( e) => {
3432- if is_blocking_io_error ( & e, vm) {
3433- return Err (
3434- create_ssl_want_write_error ( vm) . upcast ( )
3435- ) ;
3441+ let to_send = buf[ sent_total..] . to_vec ( ) ;
3442+ match self . sock_send ( to_send, vm) {
3443+ Ok ( result) => {
3444+ let sent: usize = result
3445+ . try_to_value :: < isize > ( vm) ?
3446+ . try_into ( )
3447+ . unwrap_or ( 0 ) ;
3448+ if sent == 0 {
3449+ // No progress - socket buffer full
3450+ if is_non_blocking {
3451+ // Non-blocking: must return to avoid infinite loop
3452+ return Err ( create_ssl_want_write_error (
3453+ vm,
3454+ )
3455+ . upcast ( ) ) ;
3456+ }
3457+ // Blocking: retry
3458+ continue ;
3459+ }
3460+ sent_total += sent;
3461+ }
3462+ Err ( e) => {
3463+ if is_blocking_io_error ( & e, vm) {
3464+ if is_non_blocking {
3465+ // Non-blocking: must return to avoid infinite loop
3466+ return Err ( create_ssl_want_write_error (
3467+ vm,
3468+ )
3469+ . upcast ( ) ) ;
3470+ }
3471+ // Blocking: retry
3472+ continue ;
3473+ }
3474+ return Err ( e) ;
34363475 }
3437- return Err ( e) ;
34383476 }
34393477 }
34403478 }
@@ -3775,46 +3813,61 @@ mod _ssl {
37753813
37763814 // If peer hasn't closed yet, try to read from socket
37773815 if !peer_closed {
3778- // Check if socket is in blocking mode ( timeout is None)
3779- let is_blocking = if !is_bio {
3816+ // Check socket timeout mode
3817+ let timeout_mode = if !is_bio {
37803818 // Get socket timeout
37813819 match self . sock . get_attr ( "gettimeout" , vm) {
37823820 Ok ( method) => match method. call ( ( ) , vm) {
3783- Ok ( timeout) => vm. is_none ( & timeout) ,
3784- Err ( _) => false ,
3821+ Ok ( timeout) => {
3822+ if vm. is_none ( & timeout) {
3823+ // timeout=None means blocking
3824+ Some ( None )
3825+ } else if let Ok ( t) = timeout. try_float ( vm) . map ( |f| f. to_f64 ( ) ) {
3826+ if t == 0.0 {
3827+ // timeout=0 means non-blocking
3828+ Some ( Some ( 0.0 ) )
3829+ } else {
3830+ // timeout>0 means timeout mode
3831+ Some ( Some ( t) )
3832+ }
3833+ } else {
3834+ None
3835+ }
3836+ }
3837+ Err ( _) => None ,
37853838 } ,
3786- Err ( _) => false ,
3839+ Err ( _) => None ,
37873840 }
37883841 } else {
3789- false
3842+ None // BIO mode
37903843 } ;
37913844
37923845 if is_bio {
37933846 // In BIO mode: non-blocking read attempt
3794- let _ = self . try_read_close_notify ( conn, vm) ;
3795- } else if is_blocking {
3796- // Blocking socket mode: Return immediately without waiting for peer
3797- //
3798- // Reasons we don't read from socket here:
3799- // 1. STARTTLS scenario: application data may arrive before/instead of close_notify
3800- // - Example: client sends ENDTLS, immediately sends plain "msg 5"
3801- // - Server's unwrap() would read "msg 5" and try to parse as TLS → FAIL
3802- // 2. CPython's SSL_shutdown() typically returns immediately without waiting
3803- // 3. Bidirectional shutdown is the application's responsibility
3804- // 4. Reading from socket would consume application data incorrectly
3847+ if self . try_read_close_notify ( conn, vm) ? {
3848+ peer_closed = true ;
3849+ }
3850+ } else if let Some ( _timeout) = timeout_mode {
3851+ // All socket modes (blocking, timeout, non-blocking):
3852+ // Return immediately after sending our close_notify.
38053853 //
3806- // Therefore: Just send our close_notify and return success immediately.
3807- // The peer's close_notify (if any) will remain in the socket buffer.
3854+ // This matches CPython/OpenSSL behavior where SSL_shutdown()
3855+ // returns after sending close_notify, allowing the app to
3856+ // close the socket without waiting for peer's close_notify.
38083857 //
3809- // Mark shutdown as complete and return the underlying socket
3858+ // Waiting for peer's close_notify can cause deadlock with
3859+ // asyncore-based servers where both sides wait for the other's
3860+ // close_notify before closing the connection.
38103861 drop ( conn_guard) ;
38113862 * self . shutdown_state . lock ( ) = ShutdownState :: Completed ;
38123863 * self . connection . lock ( ) = None ;
38133864 return Ok ( self . sock . clone ( ) ) ;
38143865 }
38153866
38163867 // Step 3: Check again if peer has sent close_notify (non-blocking/BIO mode only)
3817- peer_closed = self . check_peer_closed ( conn, vm) ?;
3868+ if !peer_closed {
3869+ peer_closed = self . check_peer_closed ( conn, vm) ?;
3870+ }
38183871 }
38193872
38203873 drop ( conn_guard) ; // Release lock before returning
@@ -3850,42 +3903,96 @@ mod _ssl {
38503903 break ;
38513904 }
38523905
3853- // Send to outgoing BIO or socket
3854- self . sock_send ( buf[ ..written] . to_vec ( ) , vm) ?;
3906+ // Check if this is a non-blocking socket
3907+ let timeout = self . get_socket_timeout ( vm) ?;
3908+ let is_non_blocking = timeout. map ( |t| t. is_zero ( ) ) . unwrap_or ( false ) ;
3909+
3910+ // Send to outgoing BIO or socket, handling partial sends
3911+ let data = buf[ ..written] . to_vec ( ) ;
3912+ let mut sent_total = 0 ;
3913+ while sent_total < data. len ( ) {
3914+ let to_send = data[ sent_total..] . to_vec ( ) ;
3915+ match self . sock_send ( to_send, vm) {
3916+ Ok ( result) => {
3917+ let sent: usize =
3918+ result. try_to_value :: < isize > ( vm) ?. try_into ( ) . unwrap_or ( 0 ) ;
3919+ if sent == 0 {
3920+ // Would block
3921+ if is_non_blocking {
3922+ // Non-blocking socket: return WantWrite to avoid infinite loop
3923+ return Err ( create_ssl_want_write_error ( vm) . upcast ( ) ) ;
3924+ }
3925+ // Wait for socket to be ready
3926+ let timed_out =
3927+ self . sock_wait_for_io_impl ( SelectKind :: Write , vm) ?;
3928+ if timed_out {
3929+ return Err ( vm. new_os_error ( "Write operation timed out" ) ) ;
3930+ }
3931+ continue ;
3932+ }
3933+ sent_total += sent;
3934+ }
3935+ Err ( e) => {
3936+ if is_blocking_io_error ( & e, vm) {
3937+ if is_non_blocking {
3938+ // Non-blocking socket: return WantWrite
3939+ return Err ( create_ssl_want_write_error ( vm) . upcast ( ) ) ;
3940+ }
3941+ // Wait for socket to be ready
3942+ let timed_out =
3943+ self . sock_wait_for_io_impl ( SelectKind :: Write , vm) ?;
3944+ if timed_out {
3945+ return Err ( vm. new_os_error ( "Write operation timed out" ) ) ;
3946+ }
3947+ continue ;
3948+ }
3949+ return Err ( e) ;
3950+ }
3951+ }
3952+ }
38553953 }
38563954
38573955 Ok ( ( ) )
38583956 }
38593957
3860- // Helper: Try to read incoming data from BIO (non-blocking)
3958+ // Helper: Try to read incoming data from socket/BIO
3959+ // Returns true if peer closed connection (with or without close_notify)
38613960 fn try_read_close_notify (
38623961 & self ,
38633962 conn : & mut TlsConnection ,
38643963 vm : & VirtualMachine ,
3865- ) -> PyResult < ( ) > {
3866- // Try to read incoming data from BIO
3867- // This is non-blocking in BIO mode - if no data, recv returns empty
3964+ ) -> PyResult < bool > {
3965+ // Try to read incoming data
38683966 match self . sock_recv ( SSL3_RT_MAX_PLAIN_LENGTH , vm) {
38693967 Ok ( bytes_obj) => {
38703968 let bytes = ArgBytesLike :: try_from_object ( vm, bytes_obj) ?;
38713969 let data = bytes. borrow_buf ( ) ;
38723970
3873- if !data. is_empty ( ) {
3874- // Feed data to TLS connection
3875- let data_slice: & [ u8 ] = data. as_ref ( ) ;
3876- let mut cursor = std:: io:: Cursor :: new ( data_slice) ;
3877- let _ = conn. read_tls ( & mut cursor) ;
3878-
3879- // Process packets
3880- let _ = conn. process_new_packets ( ) ;
3971+ if data. is_empty ( ) {
3972+ // Empty read = EOF = peer closed connection
3973+ // This is "ragged EOF" - peer closed without close_notify
3974+ return Ok ( true ) ;
38813975 }
3976+
3977+ // Feed data to TLS connection
3978+ let data_slice: & [ u8 ] = data. as_ref ( ) ;
3979+ let mut cursor = std:: io:: Cursor :: new ( data_slice) ;
3980+ let _ = conn. read_tls ( & mut cursor) ;
3981+
3982+ // Process packets
3983+ let _ = conn. process_new_packets ( ) ;
3984+ Ok ( false )
38823985 }
3883- Err ( _) => {
3884- // No data available or error - that's OK in BIO mode
3986+ Err ( e) => {
3987+ // BlockingIOError means no data yet
3988+ if is_blocking_io_error ( & e, vm) {
3989+ return Ok ( false ) ;
3990+ }
3991+ // Connection reset, EOF, or other error means peer closed
3992+ // ECONNRESET, EPIPE, broken pipe, etc.
3993+ Ok ( true )
38853994 }
38863995 }
3887-
3888- Ok ( ( ) )
38893996 }
38903997
38913998 // Helper: Check if peer has sent close_notify
0 commit comments