@@ -1552,6 +1552,11 @@ pub(super) fn ssl_read(
15521552
15531553 // Try to read plaintext from rustls buffer
15541554 if let Some ( n) = try_read_plaintext ( conn, buf) ? {
1555+ if n == 0 {
1556+ // EOF from TLS - close_notify received
1557+ // Return ZeroReturn so Python raises SSLZeroReturnError
1558+ return Err ( SslError :: ZeroReturn ) ;
1559+ }
15551560 return Ok ( n) ;
15561561 }
15571562
@@ -1740,17 +1745,40 @@ pub(super) fn ssl_write(
17401745 let already_buffered = * socket. write_buffered_len . lock ( ) ;
17411746
17421747 // Only write plaintext if not already buffered
1748+ // Track how much we wrote for partial write handling
1749+ let mut bytes_written_to_rustls = 0usize ;
1750+
17431751 if already_buffered == 0 {
17441752 // Write plaintext to rustls (= SSL_write_ex internal buffer write)
1745- {
1753+ bytes_written_to_rustls = {
17461754 let mut writer = conn. writer ( ) ;
17471755 use std:: io:: Write ;
1748- writer
1749- . write_all ( data)
1750- . map_err ( |e| SslError :: Syscall ( format ! ( "Write failed: {e}" ) ) ) ?;
1751- }
1752- // Mark data as buffered
1753- * socket. write_buffered_len . lock ( ) = data. len ( ) ;
1756+ // Use write() instead of write_all() to support partial writes.
1757+ // In BIO mode (asyncio), when the internal buffer is full,
1758+ // we want to write as much as possible and return that count,
1759+ // rather than failing completely.
1760+ match writer. write ( data) {
1761+ Ok ( 0 ) if !data. is_empty ( ) => {
1762+ // Buffer is full and nothing could be written.
1763+ // In BIO mode, return WantWrite so the caller can
1764+ // drain the outgoing BIO and retry.
1765+ if is_bio {
1766+ return Err ( SslError :: WantWrite ) ;
1767+ }
1768+ return Err ( SslError :: Syscall ( "Write failed: buffer full" . to_string ( ) ) ) ;
1769+ }
1770+ Ok ( n) => n,
1771+ Err ( e) => {
1772+ if is_bio {
1773+ // In BIO mode, treat write errors as WantWrite
1774+ return Err ( SslError :: WantWrite ) ;
1775+ }
1776+ return Err ( SslError :: Syscall ( format ! ( "Write failed: {e}" ) ) ) ;
1777+ }
1778+ }
1779+ } ;
1780+ // Mark data as buffered (only the portion we actually wrote)
1781+ * socket. write_buffered_len . lock ( ) = bytes_written_to_rustls;
17541782 } else if already_buffered != data. len ( ) {
17551783 // Caller is retrying with different data - this is a protocol error
17561784 // Clear the buffer state and return an SSL error (bad write retry)
@@ -1790,13 +1818,23 @@ pub(super) fn ssl_write(
17901818 }
17911819 Err ( SslError :: WantWrite ) => {
17921820 // Non-blocking socket would block - return WANT_WRITE
1821+ // If we had a partial write to rustls, return partial success
1822+ // instead of error to match OpenSSL partial-write semantics
1823+ if bytes_written_to_rustls > 0 && bytes_written_to_rustls < data. len ( ) {
1824+ * socket. write_buffered_len . lock ( ) = 0 ;
1825+ return Ok ( bytes_written_to_rustls) ;
1826+ }
17931827 // Keep write_buffered_len set so we don't re-buffer on retry
17941828 return Err ( SslError :: WantWrite ) ;
17951829 }
17961830 Err ( SslError :: WantRead ) => {
17971831 // Need to read before write can complete (e.g., renegotiation)
1798- // This matches CPython's handling of SSL_ERROR_WANT_READ in write
17991832 if is_bio {
1833+ // If we had a partial write to rustls, return partial success
1834+ if bytes_written_to_rustls > 0 && bytes_written_to_rustls < data. len ( ) {
1835+ * socket. write_buffered_len . lock ( ) = 0 ;
1836+ return Ok ( bytes_written_to_rustls) ;
1837+ }
18001838 // Keep write_buffered_len set so we don't re-buffer on retry
18011839 return Err ( SslError :: WantRead ) ;
18021840 }
@@ -1807,6 +1845,11 @@ pub(super) fn ssl_write(
18071845 // Continue loop
18081846 }
18091847 Err ( e @ SslError :: Timeout ( _) ) => {
1848+ // If we had a partial write to rustls, return partial success
1849+ if bytes_written_to_rustls > 0 && bytes_written_to_rustls < data. len ( ) {
1850+ * socket. write_buffered_len . lock ( ) = 0 ;
1851+ return Ok ( bytes_written_to_rustls) ;
1852+ }
18101853 // Preserve buffered state so retry doesn't duplicate data
18111854 // (send_all_bytes saved unsent TLS bytes to pending_tls_output)
18121855 return Err ( e) ;
@@ -1826,10 +1869,21 @@ pub(super) fn ssl_write(
18261869 . map_err ( SslError :: Py ) ?;
18271870 }
18281871
1872+ // Determine how many bytes we actually wrote
1873+ let actual_written = if bytes_written_to_rustls > 0 {
1874+ // Fresh write: return what we wrote to rustls
1875+ bytes_written_to_rustls
1876+ } else if already_buffered > 0 {
1877+ // Retry of previous write: return the full buffered amount
1878+ already_buffered
1879+ } else {
1880+ data. len ( )
1881+ } ;
1882+
18291883 // Write completed successfully - clear buffer state
18301884 * socket. write_buffered_len . lock ( ) = 0 ;
18311885
1832- Ok ( data . len ( ) )
1886+ Ok ( actual_written )
18331887}
18341888
18351889// Helper functions (private-ish, used by public SSL functions)
0 commit comments