@@ -3131,6 +3131,195 @@ def test_wantWriteError(self) -> None:
31313131
31323132 # XXX want_read
31333133
3134+ def _fill_client_buffer (self , client_socket : socket ) -> None :
3135+ """
3136+ Attempts to fill the client's raw send buffer until
3137+ EWOULDBLOCK is hit.
3138+ """
3139+ print ("--- Phase 1: Filling client socket buffer ---" )
3140+ for msg in [b"x" * 65536 , b"x" * 16 ]:
3141+ for _ in range (
3142+ 1024 * 1024 * 64
3143+ ): # Large loop count to ensure buffer fill
3144+ try :
3145+ client_socket .send (msg )
3146+ time .sleep (0.01 )
3147+ except OSError as e :
3148+ if e .errno == EWOULDBLOCK :
3149+ print ("Client socket buffer filled (EWOULDBLOCK hit)." )
3150+ return # Buffer successfully filled, exit function
3151+ raise # pragma: no cover # Re-raise unexpected OSErrors
3152+ else : # If the inner loop completes without hitting EWOULDBLOCK
3153+ pytest .fail (
3154+ "Failed to fill socket buffer, cannot test bad write error"
3155+ ) # pragma: no cover
3156+
3157+ def _attempt_want_write_error (self , client : Connection ) -> int :
3158+ """
3159+ Attempts to send application data over SSL to trigger WantWriteError.
3160+ Returns (True, successful_size) if triggered,
3161+ otherwise calls pytest.fail.
3162+ """
3163+ print ("--- Phase 2: Attempting to trigger WantWriteError ---" )
3164+ test_sizes = [
3165+ 64 ,
3166+ 128 ,
3167+ 256 ,
3168+ 512 ,
3169+ 1024 ,
3170+ 2048 ,
3171+ 4096 ,
3172+ 8192 ,
3173+ 16384 ,
3174+ 32768 ,
3175+ 65536 ,
3176+ ]
3177+ initial_want_write_triggered = False
3178+ successful_size = - 1
3179+
3180+ for size in test_sizes :
3181+ msg2 = b"Y" * size
3182+ try :
3183+ client .send (msg2 )
3184+ # Continue loop to try larger sizes until an error is hit
3185+ except SSL .WantWriteError :
3186+ print (
3187+ f"Got WantWriteError with message size { size } "
3188+ "(this is what we want)."
3189+ )
3190+ initial_want_write_triggered = True
3191+ successful_size = size * 2 # double it to be really sure
3192+ break # Exit loop as desired error was triggered
3193+
3194+ if not initial_want_write_triggered :
3195+ pytest .fail (
3196+ "Could not induce WantWriteError with any message size."
3197+ ) # pragma: no cover
3198+
3199+ return successful_size
3200+
3201+ def _drain_server_buffers (
3202+ self , server : Connection , server_socket : socket
3203+ ) -> None :
3204+ """Reads from server SSL and raw sockets to drain any pending data."""
3205+ print ("--- Phase 3: Draining server buffers ---" )
3206+ total_read = 0
3207+ read_chunks = []
3208+
3209+ try :
3210+ # First, try to read any SSL data that might be available
3211+ try :
3212+ server .recv (65536 )
3213+ except (SSL .WantReadError , SSL .Error ) as ssl_error :
3214+ print (f"No SSL data available or SSL error: { ssl_error } " )
3215+
3216+ # Now read raw data from the underlying server socket to
3217+ # drain buffer
3218+ server_socket .setblocking (False ) # Ensure non-blocking
3219+ consecutive_empty_reads = 0
3220+
3221+ while (
3222+ total_read < 1024 * 1024
3223+ ): # Read up to 1MB or until no more data
3224+ try :
3225+ data = server_socket .recv (65536 ) # Read raw data
3226+ read_chunks .append (data )
3227+ total_read += len (data )
3228+ print (
3229+ f"Read { len (data )} bytes of raw data from "
3230+ f"server socket (total: { total_read } )."
3231+ )
3232+ time .sleep (0.01 ) # Small delay between reads
3233+
3234+ except OSError as e :
3235+ if e .errno == EWOULDBLOCK :
3236+ consecutive_empty_reads += 1
3237+ if consecutive_empty_reads >= 5 :
3238+ print (
3239+ "No more raw data available from server "
3240+ "socket after retries."
3241+ )
3242+ break
3243+ print (
3244+ "No data available, waiting... "
3245+ f"(attempt { consecutive_empty_reads } )."
3246+ )
3247+ time .sleep (0.1 ) # Wait longer when buffer is empty
3248+ continue
3249+ else : # pragma: no cover
3250+ print (f"OSError while reading from server socket: { e } " )
3251+ break
3252+
3253+ print (
3254+ f"Finished reading from server. Total bytes read: { total_read } "
3255+ )
3256+ print ("Allowing network buffers to settle..." )
3257+ time .sleep (0.1 )
3258+
3259+ except Exception as read_exception : # pragma: no cover
3260+ print (f"Exception while reading from server: { read_exception } " )
3261+
3262+ def _perform_moving_buffer_test (
3263+ self , client : Connection , successful_size : int
3264+ ) -> bool :
3265+ """
3266+ Attempts a retry write with a moving buffer and checks for
3267+ 'bad write retry' error.
3268+ Returns True if 'bad write retry' occurs, False otherwise.
3269+ """
3270+ print ("--- Phase 4: Performing moving buffer retry test ---" )
3271+ assert successful_size > - 1 , (
3272+ "successful_size must be greater than -1 for a WantWriteError "
3273+ "to be triggered"
3274+ )
3275+ msg3 = b"Z" * successful_size
3276+
3277+ print (
3278+ "Attempting retry with different buffer "
3279+ f"(same size { successful_size } )."
3280+ )
3281+
3282+ try :
3283+ client .send (msg3 )
3284+ print (f"Retry succeeded with { successful_size } bytes written." )
3285+ return False # Retry succeeded
3286+ except SSL .Error as e :
3287+ reason = get_ssl_error_reason (e )
3288+ if reason == "bad write retry" :
3289+ print (f"Got expected SSL error: { e } ({ reason } )." )
3290+ return True # Bad write retry
3291+ else :
3292+ pytest .fail (
3293+ f"Retry failed with unexpected SSL error: { e } ({ reason } )."
3294+ ) # pragma: no cover
3295+ # If any other exception occurs, it will propagate up
3296+
3297+ def _shutdown_connections (
3298+ self ,
3299+ client : Connection ,
3300+ server : Connection ,
3301+ client_socket : socket ,
3302+ server_socket : socket ,
3303+ ) -> None :
3304+ """Helper to safely shut down SSL connections and close sockets."""
3305+ print ("--- Cleanup: Shutting down connections ---" )
3306+ try :
3307+ if client :
3308+ client .shutdown ()
3309+ except Exception as e :
3310+ print (f"Error during client SSL shutdown: { e } " )
3311+ try :
3312+ if server :
3313+ server .shutdown ()
3314+ except Exception as e :
3315+ print (f"Error during server SSL shutdown: { e } " )
3316+ finally :
3317+ if client_socket :
3318+ client_socket .close ()
3319+ if server_socket :
3320+ server_socket .close ()
3321+ print ("Connections closed." )
3322+
31343323 def _badwriteretry (self , mode : int ) -> bool :
31353324 """
31363325 Tries to force a "bad write retry" error over an SSL connection
@@ -3141,78 +3330,26 @@ def _badwriteretry(self, mode: int) -> bool:
31413330 create_ssl_nonblocking_connection (mode )
31423331 )
31433332 result = False # Default return value
3144- written = 0
31453333
31463334 try :
3147- # Fill up the client's raw send buffer so the SSL connection
3148- # won't be able to write anything. Start by sending larger chunks
3149- # and continue by writing smaller chunks so we can be sure we
3150- # completely fill the buffer.
3151- for msg in [b"x" * 65536 , b"x" * 16 ]:
3152- for i in range (1024 * 1024 * 64 ):
3153- try :
3154- written = client_socket .send (msg )
3155- print (f"Sent { written } bytes to fill buffer" )
3156- except OSError as e :
3157- if e .errno == EWOULDBLOCK :
3158- break
3159- raise
3160- else :
3161- pytest .fail (
3162- "Failed to fill socket buffer, cannot test \
3163- bad write error"
3164- )
3165-
3166- # Now, attempt to send application data over the established
3167- # SSL connection. Since the underlying raw socket's buffer is full,
3168- # this should cause a WantWriteError.
3169- msg2 = b"Y" * 65536
3170-
3171- try :
3172- written = client .send (msg2 )
3173- except SSL .WantWriteError :
3174- try :
3175- # After a WantWriteError if the connection has partially
3176- # written the last buffer it will expect a retry write.
3177- # This next write should fail but for two different reasons
3178- # depending on whether SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER
3179- # was set
3180- msg3 = b"Z" * 65536
3181- written = client .send (msg3 )
3182- pytest .fail ("Retry succeeded unexpectedly" )
3183- except SSL .Error as e :
3184- reason = get_ssl_error_reason (e )
3185- if reason == "bad write retry" :
3186- # Got SSL error on retry (expected if not using \
3187- # SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER)
3188- result = True
3189- else :
3190- # when using SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER
3191- # we expect this to fail for a WantWriteError
3192- result = False
3193-
3194- except SSL .Error as e :
3195- reason = get_ssl_error_reason (e )
3196- pytest .fail (f"Got unexpected SSL error on retry: { e } { reason } " )
3197- except Exception as e :
3198- pytest .fail (f"Unexpected exception during send: { e } " )
3199-
3335+ # --- Main Test Flow ---
3336+ self ._fill_client_buffer (client_socket )
3337+
3338+ successful_size = self ._attempt_want_write_error (client )
3339+
3340+ # if WantWriteError was not triggered the test fails in
3341+ # _attempt_want_write_error().
3342+ # proceed with draining and retry
3343+ self ._drain_server_buffers (server , server_socket )
3344+ result = self ._perform_moving_buffer_test (client , successful_size )
3345+ except Exception as e : # pragma: no cover
3346+ pytest .fail (f"Unexpected exception during test: { e } ." )
32003347 finally :
3201- # Cleanup: shut down SSL connections and close raw sockets
3202- try :
3203- if client :
3204- client .shutdown ()
3205- if server :
3206- server .shutdown ()
3207- except Exception as e :
3208- print (f"Error during SSL shutdown: { e } " )
3209- finally :
3210- if client_socket :
3211- client_socket .close ()
3212- if server_socket :
3213- server_socket .close ()
3214-
3215- return result # Return the result after cleanup
3348+ self ._shutdown_connections (
3349+ client , server , client_socket , server_socket
3350+ )
3351+
3352+ return result
32163353
32173354 def test_moving_write_buffer_should_pass (self ) -> None :
32183355 """
@@ -3227,11 +3364,10 @@ def test_moving_write_buffer_should_pass(self) -> None:
32273364 )
32283365 result = self ._badwriteretry (mode )
32293366
3230- if result :
3231- pytest .fail (
3232- "Using SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER failed to \
3233- prevent bad write retry"
3234- )
3367+ assert result is False , (
3368+ "Using SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER failed to prevent bad "
3369+ "write retry. A bad write retry occurred when it should not have."
3370+ )
32353371
32363372 def test_moving_write_buffer_should_fail (self ) -> None :
32373373 """
@@ -3244,12 +3380,11 @@ def test_moving_write_buffer_should_fail(self) -> None:
32443380 mode = 0
32453381 result = self ._badwriteretry (mode )
32463382
3247- if not result :
3248- pytest .fail (
3249- "Use of a moving buffer without \
3250- SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER should trigger \
3251- a bad write retry error"
3252- )
3383+ assert result is True , (
3384+ "Use of a moving buffer without "
3385+ "SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER should trigger "
3386+ "a bad write retry error."
3387+ )
32533388
32543389 def test_get_finished_before_connect (self ) -> None :
32553390 """
0 commit comments