@@ -3811,6 +3811,70 @@ static void TestDisconnectOutranksRekey(void)
38113811}
38123812
38133813
3814+ /* disconnectTxd means "a flush is owed", not "a disconnect was sent". Once
3815+ * ours has gone out, a teardown call must not push whatever the internal
3816+ * senders queued behind it. */
3817+ static void TestDisconnectTxdClearsOnFlush (void )
3818+ {
3819+ WOLFSSH_CTX * ctx ;
3820+ WOLFSSH * ssh ;
3821+ WOLFSSH_CHANNEL * channel ;
3822+ MemIo io ;
3823+ byte out [512 ];
3824+ int ret ;
3825+
3826+ ctx = wolfSSH_CTX_new (WOLFSSH_ENDPOINT_CLIENT , NULL );
3827+ AssertNotNull (ctx );
3828+
3829+ wolfSSH_SetIORecv (ctx , MemRecv );
3830+ wolfSSH_SetIOSend (ctx , MemSendWantWrite );
3831+
3832+ ssh = wolfSSH_new (ctx );
3833+ AssertNotNull (ssh );
3834+ AddSessionChannel (ssh );
3835+ channel = ssh -> channelList ;
3836+ ssh -> connectState = CONNECT_SERVER_USERAUTH_ACCEPT_DONE ;
3837+
3838+ MemIoInit (& io , NULL , 0 , out , sizeof (out ));
3839+ wolfSSH_SetIOReadCtx (ssh , & io );
3840+ wolfSSH_SetIOWriteCtx (ssh , & io );
3841+
3842+ /* Our disconnect short-sends, so a flush is owed. */
3843+ MemSendWantWriteCount = 1 ;
3844+ AssertIntEQ (wolfSSH_SendDisconnect (ssh , WOLFSSH_DISCONNECT_BY_APPLICATION ),
3845+ WS_WANT_WRITE );
3846+ AssertTrue (ssh -> disconnectTxd );
3847+ AssertTrue (wolfSSH_OutputPending (ssh ));
3848+ AssertIntEQ (io .outSz , 0 );
3849+
3850+ /* The caller's retry loop pumps it out in full, so nothing is owed. */
3851+ AssertIntEQ (wolfSSH_SendPacket (ssh ), WS_SUCCESS );
3852+ AssertFalse (wolfSSH_OutputPending (ssh ));
3853+ AssertFalse (ssh -> disconnectTxd );
3854+ AssertIntEQ (out [LENGTH_SZ + 1 ], MSGID_DISCONNECT );
3855+ io .outSz = 0 ;
3856+
3857+ /* An in-flight CHANNEL_EOF draws a reply out of DoChannelEof(); the
3858+ * internal senders are not behind the disconnect gate. It short-sends,
3859+ * so it sits in the output buffer. */
3860+ MemSendWantWriteCount = 1 ;
3861+ AssertIntEQ (SendChannelEof (ssh , channel -> peerChannel ), WS_WANT_WRITE );
3862+ AssertTrue (wolfSSH_OutputPending (ssh ));
3863+ AssertIntEQ (io .outSz , 0 );
3864+
3865+ /* Teardown must leave it there: the disconnect is already gone, so this
3866+ * would be traffic after it. RFC 4253 section 11.1. */
3867+ ret = wolfSSH_shutdown (ssh );
3868+ AssertIntEQ (ret , WS_SUCCESS );
3869+ AssertIntEQ (wolfSSH_get_error (ssh ), WS_DISCONNECT );
3870+ AssertIntEQ (io .outSz , 0 );
3871+ AssertTrue (wolfSSH_OutputPending (ssh ));
3872+
3873+ wolfSSH_free (ssh );
3874+ wolfSSH_CTX_free (ctx );
3875+ }
3876+
3877+
38143878/* A flush that is itself short owns ssh->error. Callers gate their retry on
38153879 * WS_WANT_WRITE, so the disconnect gate must not overwrite it. */
38163880static void TestShutdownKeepsFlushWantWrite (void )
@@ -7644,6 +7708,7 @@ int main(int argc, char** argv)
76447708 TestQueuedDisconnectFlushes ();
76457709 TestShutdownFlushesQueuedDisconnect ();
76467710 TestShutdownKeepsFlushWantWrite ();
7711+ TestDisconnectTxdClearsOnFlush ();
76477712 TestDisconnectOutranksRekey ();
76487713#if defined(WOLFSSH_TERM ) && !defined(NO_FILESYSTEM )
76497714 TestTerminalResizeBlockedAfterDisconnect ();
0 commit comments