@@ -6488,6 +6488,220 @@ static int test_SendChannelData_zeroPeerMaxPacket(void)
64886488 return result;
64896489}
64906490
6491+ #ifndef NO_WOLFSSH_SERVER
6492+
6493+ /* wolfSSH_stream_read() counterpart of test_ChannelExtDataCreditWantWrite():
6494+ * a deferred credit must not cost the caller the bytes already consumed, and
6495+ * an adjust that fails outright must still surface on wolfSSH_get_error(). */
6496+ static int test_stream_read_deferredWindowAdjust(void)
6497+ {
6498+ WOLFSSH_CTX* ctx = NULL;
6499+ WOLFSSH* ssh = NULL;
6500+ WOLFSSH_CHANNEL* ch = NULL;
6501+ int result = 0;
6502+ int ret;
6503+ byte in[64];
6504+ byte out[64];
6505+ word32 i;
6506+
6507+ for (i = 0; i < (word32)sizeof(in); i++) {
6508+ in[i] = (byte)i;
6509+ }
6510+
6511+ ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
6512+ if (ctx == NULL)
6513+ return -6980;
6514+ wolfSSH_SetIOSend(ctx, WantWriteIoSend);
6515+
6516+ ssh = wolfSSH_new(ctx);
6517+ if (ssh == NULL) { result = -6981; goto done; }
6518+ /* Allow MSGID_CHANNEL_WINDOW_ADJUST on this bare session. */
6519+ ssh->acceptState = ACCEPT_SERVER_USERAUTH_SENT;
6520+
6521+ /* A window the size of the payload, so draining it in one read leaves
6522+ * windowSz at zero and _UpdateChannelWindow() has to credit. */
6523+ ch = ChannelNew(ssh, ID_CHANTYPE_SESSION,
6524+ (word32)sizeof(in), DEFAULT_MAX_PACKET_SZ);
6525+ if (ch == NULL) { result = -6982; goto done; }
6526+ if (ChannelAppend(ssh, ch) != WS_SUCCESS) {
6527+ ChannelDelete(ch, ssh->ctx->heap);
6528+ result = -6983;
6529+ goto done;
6530+ }
6531+ ch->openConfirmed = 1;
6532+
6533+ if (wolfSSH_TestChannelPutData(ch, in, (word32)sizeof(in)) != WS_SUCCESS) {
6534+ result = -6984; goto done;
6535+ }
6536+ if (ch->windowSz != 0) { result = -6985; goto done; }
6537+
6538+ /* Every byte is reported, and they are the bytes that were put. */
6539+ ret = wolfSSH_stream_read(ssh, out, (word32)sizeof(out));
6540+ if (ret != (int)sizeof(in)) { result = -6990; goto done; }
6541+ if (WMEMCMP(out, in, sizeof(in)) != 0) { result = -6991; goto done; }
6542+
6543+ /* The deferral is observable, the window is credited locally, and the
6544+ * bytes are consumed rather than left for a re-read. */
6545+ if (ssh->error != WS_WANT_WRITE) { result = -6992; goto done; }
6546+ if (ch->windowSz != (word32)sizeof(in)) { result = -6993; goto done; }
6547+ if (ch->inputBuffer.length - ch->inputBuffer.idx != 0) {
6548+ result = -6994; goto done;
6549+ }
6550+
6551+ /* A peer that reset rather than blocked. wolfSSH_SendPacket() records only
6552+ * WS_WANT_WRITE, so the read path has to record a hard failure itself. */
6553+ wolfSSH_SetIOSend(ctx, FailIoSend);
6554+
6555+ if (wolfSSH_TestChannelPutData(ch, in, (word32)sizeof(in)) != WS_SUCCESS) {
6556+ result = -6995; goto done;
6557+ }
6558+ if (ch->windowSz != 0) { result = -6996; goto done; }
6559+
6560+ ret = wolfSSH_stream_read(ssh, out, (word32)sizeof(out));
6561+ if (ret != (int)sizeof(in)) { result = -6997; goto done; }
6562+ if (wolfSSH_get_error(ssh) != WS_SOCKET_ERROR_E) {
6563+ result = -6998; goto done;
6564+ }
6565+ /* The send discarded what it bundled, so the credit stays owed. */
6566+ if (ch->pendingWindowAdjust != (word32)sizeof(in)) {
6567+ result = -6999; goto done;
6568+ }
6569+
6570+ /* Credit is charged for the bytes this read took, so a read that drains
6571+ * the window puts an adjust on the wire. */
6572+ wolfSSH_SetIOSend(ctx, CountIoSend);
6573+ s_extSendCount = 0;
6574+
6575+ if (wolfSSH_TestChannelPutData(ch, in, (word32)sizeof(in)) != WS_SUCCESS) {
6576+ result = -6970; goto done;
6577+ }
6578+
6579+ ret = wolfSSH_stream_read(ssh, out, (word32)sizeof(out));
6580+ if (ret != (int)sizeof(in)) { result = -6971; goto done; }
6581+ if (s_extSendCount != 1) { result = -6972; goto done; }
6582+ if (wolfSSH_get_error(ssh) != WS_SUCCESS) { result = -6973; goto done; }
6583+ /* The owed credit from the failed send rode along with this one. */
6584+ if (ch->pendingWindowAdjust != 0) { result = -6974; goto done; }
6585+ if (ch->windowSz != (word32)sizeof(in)) { result = -6975; goto done; }
6586+
6587+ done:
6588+ wolfSSH_free(ssh);
6589+ wolfSSH_CTX_free(ctx);
6590+ return result;
6591+ }
6592+
6593+ /* wolfSSH_ChannelIdRead() counterpart of
6594+ * test_stream_read_deferredWindowAdjust(): callers break out on a non-positive
6595+ * read, and this entry point has to retire the owed-flush status itself. */
6596+ static int test_ChannelIdRead_deferredWindowAdjust(void)
6597+ {
6598+ WOLFSSH_CTX* ctx = NULL;
6599+ WOLFSSH* ssh = NULL;
6600+ WOLFSSH_CHANNEL* ch = NULL;
6601+ int result = 0;
6602+ int ret;
6603+ byte in[64];
6604+ byte out[64];
6605+ word32 i;
6606+
6607+ for (i = 0; i < (word32)sizeof(in); i++) {
6608+ in[i] = (byte)i;
6609+ }
6610+
6611+ ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
6612+ if (ctx == NULL)
6613+ return -7010;
6614+ wolfSSH_SetIOSend(ctx, WantWriteIoSend);
6615+
6616+ ssh = wolfSSH_new(ctx);
6617+ if (ssh == NULL) { result = -7011; goto done; }
6618+ /* Allow MSGID_CHANNEL_WINDOW_ADJUST on this bare session. */
6619+ ssh->acceptState = ACCEPT_SERVER_USERAUTH_SENT;
6620+
6621+ /* A window the size of the payload, so draining it in one read leaves
6622+ * windowSz at zero and _UpdateChannelWindow() has to credit. */
6623+ ch = ChannelNew(ssh, ID_CHANTYPE_SESSION,
6624+ (word32)sizeof(in), DEFAULT_MAX_PACKET_SZ);
6625+ if (ch == NULL) { result = -7012; goto done; }
6626+ if (ChannelAppend(ssh, ch) != WS_SUCCESS) {
6627+ ChannelDelete(ch, ssh->ctx->heap);
6628+ result = -7013;
6629+ goto done;
6630+ }
6631+ ch->openConfirmed = 1;
6632+
6633+ if (wolfSSH_TestChannelPutData(ch, in, (word32)sizeof(in)) != WS_SUCCESS) {
6634+ result = -7014; goto done;
6635+ }
6636+ if (ch->windowSz != 0) { result = -7015; goto done; }
6637+
6638+ /* Unlike wolfSSH_stream_read(), this entry point does not clear the error,
6639+ * so seed it: the assert below has to prove the read recorded it. */
6640+ ssh->error = WS_SUCCESS;
6641+
6642+ /* Every byte is reported, and they are the bytes that were put. */
6643+ ret = wolfSSH_ChannelIdRead(ssh, ch->channel, out, (word32)sizeof(out));
6644+ if (ret != (int)sizeof(in)) { result = -7016; goto done; }
6645+ if (WMEMCMP(out, in, sizeof(in)) != 0) { result = -7017; goto done; }
6646+
6647+ /* The deferral is observable, the window is credited locally, and the
6648+ * bytes are consumed rather than left for a re-read. */
6649+ if (ssh->error != WS_WANT_WRITE) { result = -7018; goto done; }
6650+ if (ch->windowSz != (word32)sizeof(in)) { result = -7019; goto done; }
6651+ if (ch->inputBuffer.length - ch->inputBuffer.idx != 0) {
6652+ result = -7020; goto done;
6653+ }
6654+
6655+ /* A peer that reset rather than blocked. wolfSSH_SendPacket() records only
6656+ * WS_WANT_WRITE, so the read path has to record a hard failure itself. */
6657+ wolfSSH_SetIOSend(ctx, FailIoSend);
6658+
6659+ if (wolfSSH_TestChannelPutData(ch, in, (word32)sizeof(in)) != WS_SUCCESS) {
6660+ result = -7021; goto done;
6661+ }
6662+ if (ch->windowSz != 0) { result = -7022; goto done; }
6663+
6664+ ret = wolfSSH_ChannelIdRead(ssh, ch->channel, out, (word32)sizeof(out));
6665+ if (ret != (int)sizeof(in)) { result = -7023; goto done; }
6666+ if (ssh->error != WS_SOCKET_ERROR_E) { result = -7024; goto done; }
6667+ /* The send discarded what it bundled, so the credit stays owed. */
6668+ if (ch->pendingWindowAdjust != (word32)sizeof(in)) {
6669+ result = -7025; goto done;
6670+ }
6671+
6672+ /* This entry point never resets ssh->error, so a credit that does go out
6673+ * has to retire the owed-flush status itself. */
6674+ wolfSSH_SetIOSend(ctx, DiscardIoSend);
6675+ ssh->error = WS_WANT_WRITE;
6676+
6677+ if (wolfSSH_TestChannelPutData(ch, in, (word32)sizeof(in)) != WS_SUCCESS) {
6678+ result = -7026; goto done;
6679+ }
6680+
6681+ ret = wolfSSH_ChannelIdRead(ssh, ch->channel, out, (word32)sizeof(out));
6682+ if (ret != (int)sizeof(in)) { result = -7027; goto done; }
6683+ if (ssh->outputBuffer.length != 0) { result = -7028; goto done; }
6684+ if (wolfSSH_get_error(ssh) != WS_SUCCESS) { result = -7029; goto done; }
6685+ /* Both the parked credit and the new one reached the peer. */
6686+ if (ch->pendingWindowAdjust != 0) { result = -7030; goto done; }
6687+
6688+ /* A read with nothing buffered sends no credit, so it has no standing to
6689+ * retire a WS_WANT_WRITE some other sender is still owed. */
6690+ ssh->error = WS_WANT_WRITE;
6691+
6692+ ret = wolfSSH_ChannelIdRead(ssh, ch->channel, out, (word32)sizeof(out));
6693+ if (ret != 0) { result = -7031; goto done; }
6694+ if (ssh->outputBuffer.length != 0) { result = -7032; goto done; }
6695+ if (wolfSSH_get_error(ssh) != WS_WANT_WRITE) { result = -7033; goto done; }
6696+
6697+ done:
6698+ wolfSSH_free(ssh);
6699+ wolfSSH_CTX_free(ctx);
6700+ return result;
6701+ }
6702+
6703+ #endif /* NO_WOLFSSH_SERVER */
6704+
64916705/* BuildNameList() returns a C string. On an empty id list it must still
64926706 * terminate the buffer: SendKexInit() measures the result with WSTRLEN
64936707 * through AlgoListSz() and copies that many bytes into the KEXINIT. */
@@ -17194,6 +17408,18 @@ int wolfSSH_UnitTest(int argc, char** argv)
1719417408 (unitResult == 0 ? "SUCCESS" : "FAILED"));
1719517409 testResult = testResult || unitResult;
1719617410
17411+ #ifndef NO_WOLFSSH_SERVER
17412+ unitResult = test_stream_read_deferredWindowAdjust();
17413+ printf("stream_read_deferredWindowAdjust: %s\n",
17414+ (unitResult == 0 ? "SUCCESS" : "FAILED"));
17415+ testResult = testResult || unitResult;
17416+
17417+ unitResult = test_ChannelIdRead_deferredWindowAdjust();
17418+ printf("ChannelIdRead_deferredWindowAdjust: %s\n",
17419+ (unitResult == 0 ? "SUCCESS" : "FAILED"));
17420+ testResult = testResult || unitResult;
17421+ #endif /* NO_WOLFSSH_SERVER */
17422+
1719717423 unitResult = test_BuildNameList_emptySrc();
1719817424 printf("BuildNameList_emptySrc: %s\n",
1719917425 (unitResult == 0 ? "SUCCESS" : "FAILED"));
0 commit comments