@@ -516,6 +516,7 @@ static int wolfSSH_SFTP_buffer_send(WOLFSSH* ssh, WS_SFTP_BUFFER* buffer)
516516{
517517 int ret = WS_SUCCESS ;
518518 int err ;
519+ word32 sendSz ;
519520
520521 if (buffer == NULL || ssh == NULL ) {
521522 return WS_BAD_ARGUMENT ;
@@ -564,8 +565,14 @@ static int wolfSSH_SFTP_buffer_send(WOLFSSH* ssh, WS_SFTP_BUFFER* buffer)
564565 ret = WS_SUCCESS ;
565566
566567 if (buffer -> idx < buffer -> sz ) {
567- ret = wolfSSH_stream_send (ssh , buffer -> data + buffer -> idx ,
568- buffer -> sz - buffer -> idx );
568+ sendSz = buffer -> sz - buffer -> idx ;
569+ #ifdef WOLFSSH_TEST_INTERNAL
570+ /* Test hook: force a partial send to exercise the resume path. */
571+ if (ssh -> testSftpSendCap > 0 && sendSz > ssh -> testSftpSendCap ) {
572+ sendSz = ssh -> testSftpSendCap ;
573+ }
574+ #endif
575+ ret = wolfSSH_stream_send (ssh , buffer -> data + buffer -> idx , sendSz );
569576 if (ret > 0 ) {
570577 buffer -> idx += ret ;
571578 }
@@ -577,6 +584,47 @@ static int wolfSSH_SFTP_buffer_send(WOLFSSH* ssh, WS_SFTP_BUFFER* buffer)
577584}
578585
579586
587+ /* Decide whether an SFTP request buffer has been fully handed to the peer after
588+ * a wolfSSH_SFTP_buffer_send call. Two conditions must both hold:
589+ * 1. the buffer is fully consumed (idx == sz), i.e. SendChannelData did not
590+ * clamp the send to a partial count, and
591+ * 2. nothing is still queued in the SSH output buffer; SendChannelData can
592+ * report the full dataSz while leaving the bytes buffered and returning
593+ * WS_WANT_WRITE under socket back-pressure, so idx == sz alone is not
594+ * enough.
595+ * Returns WS_SUCCESS when the request is fully sent. Otherwise sets
596+ * ssh->error = WS_WANT_WRITE and returns WS_WANT_WRITE so the caller can
597+ * preserve its state/buffer and resume on the next call. */
598+ static int wolfSSH_SFTP_buffer_send_finish (WOLFSSH * ssh ,
599+ WS_SFTP_BUFFER * buffer )
600+ {
601+ if (buffer == NULL || ssh == NULL ) {
602+ return WS_BAD_ARGUMENT ;
603+ }
604+
605+ if (wolfSSH_SFTP_buffer_idx (buffer ) < wolfSSH_SFTP_buffer_size (buffer )) {
606+ ssh -> error = WS_WANT_WRITE ;
607+ return WS_WANT_WRITE ;
608+ }
609+ #ifdef WOLFSSH_TEST_INTERNAL
610+ /* Test hook: simulate the SSH output buffer still holding queued bytes
611+ * (socket back-pressure) so the caller takes the flush-only resume path
612+ * (idx == sz, buffer_send returns WS_SUCCESS) even over loopback, where
613+ * real writes rarely block. */
614+ if (ssh -> testSftpStallPending > 0 ) {
615+ ssh -> testSftpStallPending -- ;
616+ ssh -> error = WS_WANT_WRITE ;
617+ return WS_WANT_WRITE ;
618+ }
619+ #endif
620+ if (wolfSSH_OutputPending (ssh )) {
621+ ssh -> error = WS_WANT_WRITE ;
622+ return WS_WANT_WRITE ;
623+ }
624+ return WS_SUCCESS ;
625+ }
626+
627+
580628#ifdef WOLFSSH_TEST_INTERNAL
581629int wolfSSH_TestSftpBufferSend (WOLFSSH * ssh ,
582630 byte * data , word32 sz , word32 idx )
@@ -588,6 +636,33 @@ int wolfSSH_TestSftpBufferSend(WOLFSSH* ssh,
588636 buffer .idx = idx ;
589637 return wolfSSH_SFTP_buffer_send (ssh , & buffer );
590638}
639+
640+
641+ /* Test hook: cap the number of bytes wolfSSH_SFTP_buffer_send writes per call
642+ * so a single send returns a positive-but-partial count. A cap of 0 disables
643+ * the limit. */
644+ int wolfSSH_TestSftpSendCap (WOLFSSH * ssh , word32 cap )
645+ {
646+ if (ssh == NULL ) {
647+ return WS_BAD_ARGUMENT ;
648+ }
649+ ssh -> testSftpSendCap = cap ;
650+ return WS_SUCCESS ;
651+ }
652+
653+
654+ /* Test hook: make the next `count` fully-sent SFTP buffers report as still
655+ * pending in wolfSSH_SFTP_buffer_send_finish, simulating socket back-pressure
656+ * so the flush-only resume path (idx == sz, buffer_send returns WS_SUCCESS)
657+ * can be exercised deterministically. A count of 0 disables it. */
658+ int wolfSSH_TestSftpStallPending (WOLFSSH * ssh , word32 count )
659+ {
660+ if (ssh == NULL ) {
661+ return WS_BAD_ARGUMENT ;
662+ }
663+ ssh -> testSftpStallPending = count ;
664+ return WS_SUCCESS ;
665+ }
591666#endif
592667
593668
@@ -1681,16 +1756,10 @@ int wolfSSH_SFTP_read(WOLFSSH* ssh)
16811756 }
16821757 return WS_FATAL_ERROR ;
16831758 }
1684- if (wolfSSH_SFTP_buffer_idx (& state -> buffer )
1685- < wolfSSH_SFTP_buffer_size (& state -> buffer )) {
1686- ssh -> error = WS_WANT_WRITE ;
1687- return WS_FATAL_ERROR ;
1688- }
1689- /* Check if SSH layer still has pending data from WS_WANT_WRITE.
1690- * Even if SFTP buffer is fully consumed, the data may still be
1691- * sitting in the SSH output buffer waiting to be sent. */
1692- if (wolfSSH_OutputPending (ssh )) {
1693- ssh -> error = WS_WANT_WRITE ;
1759+ /* Resume if the request is only partially sent or is still
1760+ * queued in the SSH output buffer waiting to be flushed. */
1761+ if (wolfSSH_SFTP_buffer_send_finish (ssh , & state -> buffer )
1762+ != WS_SUCCESS ) {
16941763 return WS_FATAL_ERROR ;
16951764 }
16961765 ret = WS_SUCCESS ;
@@ -7467,6 +7536,13 @@ int wolfSSH_SFTP_SetSTAT(WOLFSSH* ssh, char* dir, WS_SFTP_FILEATRB* atr)
74677536 return WS_FATAL_ERROR ;
74687537 }
74697538
7539+ /* resume if the request is only partially sent or still queued
7540+ * in the SSH output buffer */
7541+ if (wolfSSH_SFTP_buffer_send_finish (ssh , & state -> buffer )
7542+ != WS_SUCCESS ) {
7543+ return WS_FATAL_ERROR ;
7544+ }
7545+
74707546 /* free up the buffer used to send data so that a new fresh buffer
74717547 * can be created when next reading the attribute packet header */
74727548 wolfSSH_SFTP_buffer_free (ssh , & state -> buffer );
@@ -7641,6 +7717,12 @@ int wolfSSH_SFTP_Open(WOLFSSH* ssh, char* dir, word32 reason,
76417717 continue ;
76427718 }
76437719 }
7720+ /* resume if the request is only partially sent or still queued
7721+ * in the SSH output buffer */
7722+ if (wolfSSH_SFTP_buffer_send_finish (ssh , & state -> buffer )
7723+ != WS_SUCCESS ) {
7724+ return WS_FATAL_ERROR ;
7725+ }
76447726 wolfSSH_SFTP_buffer_free (ssh , & state -> buffer );
76457727 state -> state = STATE_OPEN_GETHANDLE ;
76467728 FALL_THROUGH ;
@@ -7773,6 +7855,12 @@ int wolfSSH_SFTP_SendWritePacket(WOLFSSH* ssh, byte* handle, word32 handleSz,
77737855 state -> state = STATE_SEND_WRITE_CLEANUP ;
77747856 continue ;
77757857 }
7858+ /* keep state and buffer until the header is fully flushed so
7859+ * the body is not interleaved into a half-written header */
7860+ if (wolfSSH_SFTP_buffer_send_finish (ssh , & state -> buffer )
7861+ != WS_SUCCESS ) {
7862+ return WS_FATAL_ERROR ;
7863+ }
77767864 state -> state = STATE_SEND_WRITE_SEND_BODY ;
77777865 FALL_THROUGH ;
77787866
@@ -7994,6 +8082,12 @@ int wolfSSH_SFTP_SendReadPacket(WOLFSSH* ssh, byte* handle, word32 handleSz,
79948082 state -> state = STATE_SEND_READ_CLEANUP ;
79958083 continue ;
79968084 }
8085+ /* resume if the request is only partially sent or still queued
8086+ * in the SSH output buffer */
8087+ if (wolfSSH_SFTP_buffer_send_finish (ssh , & state -> buffer )
8088+ != WS_SUCCESS ) {
8089+ return WS_FATAL_ERROR ;
8090+ }
79978091 wolfSSH_SFTP_buffer_free (ssh , & state -> buffer );
79988092 state -> state = STATE_SEND_READ_GET_HEADER ;
79998093 FALL_THROUGH ;
@@ -8229,6 +8323,13 @@ int wolfSSH_SFTP_MKDIR(WOLFSSH* ssh, char* dir, WS_SFTP_FILEATRB* atr)
82298323 return ret ;
82308324 }
82318325
8326+ /* resume if the request is only partially sent or still queued
8327+ * in the SSH output buffer */
8328+ if (wolfSSH_SFTP_buffer_send_finish (ssh , & state -> buffer )
8329+ != WS_SUCCESS ) {
8330+ return WS_FATAL_ERROR ;
8331+ }
8332+
82328333 /* free data pointer to reuse it later */
82338334 wolfSSH_SFTP_buffer_free (ssh , & state -> buffer );
82348335 state -> state = STATE_MKDIR_GET ;
@@ -8751,14 +8852,20 @@ int wolfSSH_SFTP_Rename(WOLFSSH* ssh, const char* old, const char* nw)
87518852 WLOG (WS_LOG_SFTP , "SFTP RENAME STATE: SEND" );
87528853 /* send header and type specific data */
87538854 ret = wolfSSH_SFTP_buffer_send (ssh , & state -> buffer );
8754- if (ret <= 0 ) {
8855+ if (ret < 0 ) {
87558856 if (ssh -> error == WS_WANT_READ ||
87568857 ssh -> error == WS_WANT_WRITE ) {
87578858 return WS_FATAL_ERROR ;
87588859 }
87598860 state -> state = STATE_RENAME_CLEANUP ;
87608861 continue ;
87618862 }
8863+ /* resume if the request is only partially sent or still queued
8864+ * in the SSH output buffer */
8865+ if (wolfSSH_SFTP_buffer_send_finish (ssh , & state -> buffer )
8866+ != WS_SUCCESS ) {
8867+ return WS_FATAL_ERROR ;
8868+ }
87628869 wolfSSH_SFTP_buffer_free (ssh , & state -> buffer );
87638870 state -> state = STATE_RENAME_GET_HEADER ;
87648871 FALL_THROUGH ;
0 commit comments