@@ -1769,10 +1769,16 @@ static int test_DoChannelExtendedData_overflow(void)
17691769 return result ;
17701770}
17711771
1772- /* Exercises the accumulating extended-data buffer: two stderr blobs that
1773- * arrive before the application reads must both be preserved (no silent
1774- * overwrite), and an unknown extended data type must be ignored rather than
1775- * rejected. */
1772+ /* Exercises the accumulating extended-data buffer and its window
1773+ * back-pressure: two stderr blobs that arrive before the application reads
1774+ * must both be preserved (no silent overwrite), the channel window must be
1775+ * charged on receipt and replenished on read, a partial read followed by an
1776+ * append must preserve byte ordering and window accounting across the
1777+ * GrowBuffer compaction, and an unknown extended data type must be ignored
1778+ * rather than rejected. A final case pins the documented single-session
1779+ * limitation: stderr arriving on a second channel before the first is drained
1780+ * is dropped (its window replenished immediately) rather than corrupting the
1781+ * shared buffer's window accounting. */
17761782static int test_DoChannelExtendedData_flow (void )
17771783{
17781784 WOLFSSH_CTX * ctx = NULL ;
@@ -1824,17 +1830,20 @@ static int test_DoChannelExtendedData_flow(void)
18241830 goto done ;
18251831 }
18261832
1827- /* First stderr blob: buffered. */
1833+ /* First stderr blob: buffered, window charged 128 -> 118 . */
18281834 idx = 0 ;
18291835 ret = wolfSSH_TestDoChannelExtendedData (ssh , (byte * )blob1 ,
18301836 (word32 )sizeof (blob1 ), & idx );
18311837 if (ret != WS_EXTDATA ) { result = -604 ; goto done ; }
1838+ if (ch -> windowSz != 118 ) { result = -605 ; goto done ; }
18321839
1833- /* Second stderr blob before any read: must accumulate, not overwrite. */
1840+ /* Second stderr blob before any read: must accumulate, not overwrite.
1841+ * Window charged 118 -> 108. */
18341842 idx = 0 ;
18351843 ret = wolfSSH_TestDoChannelExtendedData (ssh , (byte * )blob2 ,
18361844 (word32 )sizeof (blob2 ), & idx );
18371845 if (ret != WS_EXTDATA ) { result = -606 ; goto done ; }
1846+ if (ch -> windowSz != 108 ) { result = -607 ; goto done ; }
18381847
18391848 /* Read everything: both blobs present and in order (no data loss). */
18401849 ret = wolfSSH_extended_data_read (ssh , out , (word32 )sizeof (out ));
@@ -1846,20 +1855,178 @@ static int test_DoChannelExtendedData_flow(void)
18461855 for (i = 10 ; i < 20 ; i ++ )
18471856 if (out [i ] != 0x22 ) { result = -610 ; goto done ; }
18481857 }
1858+ /* Draining replenishes the window: 108 + 20 -> 128. */
1859+ if (ch -> windowSz != 128 ) { result = -611 ; goto done ; }
18491860
18501861 /* Buffer drained; a further read returns 0. */
18511862 ret = wolfSSH_extended_data_read (ssh , out , (word32 )sizeof (out ));
18521863 if (ret != 0 ) { result = -612 ; goto done ; }
18531864
1865+ /* Partial-read-then-append: read part of one blob (leaving idx > 0), then
1866+ * receive another blob. The append forces GrowBuffer to compact a non-zero
1867+ * idx in place (WMEMMOVE) before adding the new data, and the window must
1868+ * track a read that is split across the receipt of more data. */
1869+ idx = 0 ;
1870+ ret = wolfSSH_TestDoChannelExtendedData (ssh , (byte * )blob1 ,
1871+ (word32 )sizeof (blob1 ), & idx );
1872+ if (ret != WS_EXTDATA ) { result = -630 ; goto done ; }
1873+ if (ch -> windowSz != 118 ) { result = -631 ; goto done ; }
1874+
1875+ /* Read 4 of the 10 buffered bytes: window 118 + 4 -> 122, idx left at 4. */
1876+ ret = wolfSSH_extended_data_read (ssh , out , 4 );
1877+ if (ret != 4 ) { result = -632 ; goto done ; }
1878+ {
1879+ int i ;
1880+ for (i = 0 ; i < 4 ; i ++ )
1881+ if (out [i ] != 0x11 ) { result = -633 ; goto done ; }
1882+ }
1883+ if (ch -> windowSz != 122 ) { result = -634 ; goto done ; }
1884+
1885+ /* Append blob2 with 6 unread bytes still pending (idx=4, length=10): the
1886+ * 6 remaining 0x11 bytes must be preserved ahead of the new 0x22 bytes.
1887+ * Window charged 122 -> 112. */
1888+ idx = 0 ;
1889+ ret = wolfSSH_TestDoChannelExtendedData (ssh , (byte * )blob2 ,
1890+ (word32 )sizeof (blob2 ), & idx );
1891+ if (ret != WS_EXTDATA ) { result = -635 ; goto done ; }
1892+ if (ch -> windowSz != 112 ) { result = -636 ; goto done ; }
1893+
1894+ /* Read the rest: 6 leftover 0x11 followed by 10 0x22, in order. Window
1895+ * 112 + 16 -> 128. */
1896+ ret = wolfSSH_extended_data_read (ssh , out , (word32 )sizeof (out ));
1897+ if (ret != 16 ) { result = -637 ; goto done ; }
1898+ {
1899+ int i ;
1900+ for (i = 0 ; i < 6 ; i ++ )
1901+ if (out [i ] != 0x11 ) { result = -638 ; goto done ; }
1902+ for (i = 6 ; i < 16 ; i ++ )
1903+ if (out [i ] != 0x22 ) { result = -639 ; goto done ; }
1904+ }
1905+ if (ch -> windowSz != 128 ) { result = -640 ; goto done ; }
1906+
1907+ /* Drained again. */
1908+ ret = wolfSSH_extended_data_read (ssh , out , (word32 )sizeof (out ));
1909+ if (ret != 0 ) { result = -641 ; goto done ; }
1910+
18541911 /* Unknown extended data type: ignored (consumed), not rejected. Nothing
1855- * is buffered for the application . */
1912+ * is buffered and the window is left intact (replenished on receipt) . */
18561913 idx = 0 ;
18571914 ret = wolfSSH_TestDoChannelExtendedData (ssh , (byte * )unknownBlob ,
18581915 (word32 )sizeof (unknownBlob ), & idx );
18591916 if (ret != WS_SUCCESS ) { result = -613 ; goto done ; }
1917+ if (ch -> windowSz != 128 ) { result = -614 ; goto done ; }
18601918 ret = wolfSSH_extended_data_read (ssh , out , (word32 )sizeof (out ));
18611919 if (ret != 0 ) { result = -615 ; goto done ; }
18621920
1921+ /* Window-overflow branch: draw the window down below a blob that is
1922+ * still <= maxPacketSz, then confirm the next blob is rejected by the
1923+ * "dataSz > windowSz" branch and not the maxPacketSz branch. dataSz=60
1924+ * stays under maxPacketSz=64 so the maxPacketSz check cannot fire first. */
1925+ {
1926+ byte big [12 + 60 ];
1927+ int i ;
1928+
1929+ WMEMSET (big , 0 , sizeof (big ));
1930+ big [7 ] = 0x01 ; /* type = stderr */
1931+ big [11 ] = 0x3C ; /* dataSz = 60 */
1932+ for (i = 12 ; i < (int )sizeof (big ); i ++ )
1933+ big [i ] = 0x44 ;
1934+
1935+ /* window 128 -> 68 */
1936+ idx = 0 ;
1937+ ret = wolfSSH_TestDoChannelExtendedData (ssh , big ,
1938+ (word32 )sizeof (big ), & idx );
1939+ if (ret != WS_EXTDATA ) { result = -616 ; goto done ; }
1940+ if (ch -> windowSz != 68 ) { result = -617 ; goto done ; }
1941+
1942+ /* window 68 -> 8 */
1943+ idx = 0 ;
1944+ ret = wolfSSH_TestDoChannelExtendedData (ssh , big ,
1945+ (word32 )sizeof (big ), & idx );
1946+ if (ret != WS_EXTDATA ) { result = -618 ; goto done ; }
1947+ if (ch -> windowSz != 8 ) { result = -619 ; goto done ; }
1948+
1949+ /* dataSz=60 <= maxPacketSz but > windowSz=8: window-overflow reject,
1950+ * window left unchanged. */
1951+ idx = 0 ;
1952+ ret = wolfSSH_TestDoChannelExtendedData (ssh , big ,
1953+ (word32 )sizeof (big ), & idx );
1954+ if (ret != WS_RECV_OVERFLOW_E ) { result = -620 ; goto done ; }
1955+ if (ch -> windowSz != 8 ) { result = -621 ; goto done ; }
1956+ }
1957+
1958+ /* Cross-channel fail safe: the extData buffer and extDataChannelId are
1959+ * shared across channels, so only one channel's stderr can be buffered at a
1960+ * time. Stderr arriving on a second channel before the first is drained is
1961+ * dropped (its window replenished immediately) instead of corrupting the
1962+ * window accounting. This pins the documented single-session limitation.
1963+ * First drain the 120 bytes still pending on channel 0 from the overflow
1964+ * block (8 + 120 -> 128 on read) to reach a clean state. */
1965+ {
1966+ WOLFSSH_CHANNEL * chB = NULL ;
1967+ /* channelId=1, type=1 (stderr), dataSz=10, payload all 0x22 */
1968+ static const byte blobB [] = {
1969+ 0x00 , 0x00 , 0x00 , 0x01 ,
1970+ 0x00 , 0x00 , 0x00 , 0x01 ,
1971+ 0x00 , 0x00 , 0x00 , 0x0A ,
1972+ 0x22 , 0x22 , 0x22 , 0x22 , 0x22 , 0x22 , 0x22 , 0x22 , 0x22 , 0x22
1973+ };
1974+
1975+ ret = wolfSSH_extended_data_read (ssh , out , (word32 )sizeof (out ));
1976+ if (ret != 32 ) { result = -650 ; goto done ; }
1977+ ret = wolfSSH_extended_data_read (ssh , out , (word32 )sizeof (out ));
1978+ if (ret != 32 ) { result = -651 ; goto done ; }
1979+ ret = wolfSSH_extended_data_read (ssh , out , (word32 )sizeof (out ));
1980+ if (ret != 32 ) { result = -652 ; goto done ; }
1981+ ret = wolfSSH_extended_data_read (ssh , out , (word32 )sizeof (out ));
1982+ if (ret != 24 ) { result = -653 ; goto done ; }
1983+ ret = wolfSSH_extended_data_read (ssh , out , (word32 )sizeof (out ));
1984+ if (ret != 0 ) { result = -654 ; goto done ; }
1985+ if (ch -> windowSz != 128 ) { result = -655 ; goto done ; }
1986+
1987+ /* Second channel, id 1, same window/packet sizes as channel 0. */
1988+ chB = ChannelNew (ssh , ID_CHANTYPE_SESSION , 128 , 64 );
1989+ if (chB == NULL ) { result = -656 ; goto done ; }
1990+ if (ChannelAppend (ssh , chB ) != WS_SUCCESS ) {
1991+ ChannelDelete (chB , ssh -> ctx -> heap );
1992+ result = -657 ;
1993+ goto done ;
1994+ }
1995+
1996+ /* Channel 0 buffers stderr first: charged 128 -> 118, extDataChannelId
1997+ * becomes 0, no warning (nothing was pending). */
1998+ idx = 0 ;
1999+ ret = wolfSSH_TestDoChannelExtendedData (ssh , (byte * )blob1 ,
2000+ (word32 )sizeof (blob1 ), & idx );
2001+ if (ret != WS_EXTDATA ) { result = -658 ; goto done ; }
2002+ if (ch -> windowSz != 118 ) { result = -659 ; goto done ; }
2003+
2004+ /* Channel 1 sends stderr while channel 0 still has unread data. The
2005+ * shared single-session buffer can only hold one channel's stderr, so
2006+ * this blob is dropped (fail safe) rather than corrupting the window
2007+ * accounting. Its window is replenished immediately (SendChannelWindow-
2008+ * Adjust returns WS_SUCCESS), so chB stays at 128, the data is not
2009+ * buffered, and extDataChannelId is left at channel 0. */
2010+ idx = 0 ;
2011+ ret = wolfSSH_TestDoChannelExtendedData (ssh , (byte * )blobB ,
2012+ (word32 )sizeof (blobB ), & idx );
2013+ if (ret != WS_SUCCESS ) { result = -660 ; goto done ; }
2014+ if (chB -> windowSz != 128 ) { result = -661 ; goto done ; }
2015+
2016+ /* One read drains only channel 0's buffered 10 bytes (channel 1's were
2017+ * dropped, not buffered) and credits them back to channel 0:
2018+ * 118 + 10 -> 128. No window is leaked and no channel is over-credited. */
2019+ ret = wolfSSH_extended_data_read (ssh , out , (word32 )sizeof (out ));
2020+ if (ret != 10 ) { result = -662 ; goto done ; }
2021+ {
2022+ int i ;
2023+ for (i = 0 ; i < 10 ; i ++ )
2024+ if (out [i ] != 0x11 ) { result = -663 ; goto done ; }
2025+ }
2026+ if (ch -> windowSz != 128 ) { result = -665 ; goto done ; }
2027+ if (chB -> windowSz != 128 ) { result = -666 ; goto done ; }
2028+ }
2029+
18632030done :
18642031 wolfSSH_free (ssh );
18652032 wolfSSH_CTX_free (ctx );
0 commit comments