Skip to content

Commit b9d4cc3

Browse files
ssh, internal: flush the worker's queued output on every call
- wolfSSH_worker() calls wolfSSH_SendPacket() whenever ssh->outputBuffer holds bytes and the session is not disconnected, in place of doing so only for WS_SUCCESS, WS_WANT_READ or WS_CHAN_RXD. A failed flush reaches the return only when the receive reported nothing; a receive that reported anything but WS_WANT_READ keeps ssh->error. Drops the second DoReceive(), its WS_WINDOW_FULL case, and the WOLFSSH_TEST_BLOCK fork. - wolfSSH_SendPacket() records its code in ssh->error on every failure path: a null send callback, a bad buffer state, the connection-reset/close/general cases, and an out-of-bounds read. Its header comment states that contract. - tests cover the flush on an idle receive, the owed flush across calls, a receive failure and a hard send failure each landing in ssh->error, and that a disconnect on the same pass leaves queued output unsent.
1 parent bfe6fe0 commit b9d4cc3

4 files changed

Lines changed: 422 additions & 42 deletions

File tree

src/internal.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4290,13 +4290,15 @@ static int GetInputLine(WOLFSSH* ssh, byte** pEol)
42904290
}
42914291

42924292

4293-
/* returns WS_SUCCESS on success */
4293+
/* returns WS_SUCCESS on success. Transport failures record their code in
4294+
* ssh->error */
42944295
int wolfSSH_SendPacket(WOLFSSH* ssh)
42954296
{
42964297
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SendPacket()");
42974298

42984299
if (ssh->ctx->ioSendCb == NULL) {
42994300
WLOG(WS_LOG_DEBUG, "Your IO Send callback is null, please set");
4301+
ssh->error = WS_SOCKET_ERROR_E;
43004302
return WS_SOCKET_ERROR_E;
43014303
}
43024304

@@ -4307,6 +4309,7 @@ int wolfSSH_SendPacket(WOLFSSH* ssh)
43074309
if (ssh->outputBuffer.length > ssh->outputBuffer.bufferSz ||
43084310
ssh->outputBuffer.length < ssh->outputBuffer.idx) {
43094311
WLOG(WS_LOG_ERROR, "Bad buffer state");
4312+
ssh->error = WS_BUFFER_E;
43104313
return WS_BUFFER_E;
43114314
}
43124315

@@ -4332,11 +4335,13 @@ int wolfSSH_SendPacket(WOLFSSH* ssh)
43324335
case WS_CBIO_ERR_GENERAL:
43334336
ShrinkBuffer(&ssh->outputBuffer, 1);
43344337
}
4338+
ssh->error = WS_SOCKET_ERROR_E;
43354339
return WS_SOCKET_ERROR_E;
43364340
}
43374341

43384342
if ((word32)sent > ssh->outputBuffer.length) {
43394343
WLOG(WS_LOG_DEBUG, "wolfSSH_SendPacket() out of bounds read");
4344+
ssh->error = WS_SEND_OOB_READ_E;
43404345
return WS_SEND_OOB_READ_E;
43414346
}
43424347

src/ssh.c

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3614,46 +3614,29 @@ int wolfSSH_worker(WOLFSSH* ssh, word32* channelId)
36143614
return WS_FATAL_ERROR;
36153615
}
36163616

3617-
#ifdef WOLFSSH_TEST_BLOCK
3618-
/* In forced non-blocking test mode, keep legacy ordering (send before
3619-
* receive) to match the harness expectations and avoid synthetic spins. */
3620-
if (ret == WS_SUCCESS) {
3621-
if (ssh->outputBuffer.length != 0)
3622-
ret = wolfSSH_SendPacket(ssh);
3623-
}
3624-
if (ret == WS_SUCCESS)
3625-
ret = DoReceive(ssh);
3626-
#else
36273617
/* Always service inbound data first so window updates can unblock sends. */
36283618
if (ret == WS_SUCCESS) {
36293619
ret = DoReceive(ssh);
36303620
}
36313621

3632-
/* If receive only wanted read or delivered channel data, still try to
3633-
* flush any pending outbound packets. */
3634-
if (ret == WS_SUCCESS || ret == WS_WANT_READ || ret == WS_CHAN_RXD) {
3635-
int sendRet = WS_SUCCESS;
3636-
3637-
if (ssh->outputBuffer.length != 0)
3638-
sendRet = wolfSSH_SendPacket(ssh);
3622+
/* Flush queued output whatever DoReceive() made of the socket, since an
3623+
* idle receive reports WS_FATAL_ERROR. !ssh->disconnected gates it. */
3624+
if (ssh != NULL && !ssh->disconnected && ssh->outputBuffer.length != 0) {
3625+
int rxErr = ssh->error;
3626+
int sendRet;
36393627

3640-
/* If send is back-pressured, immediately try another receive to pick
3641-
* up potential window-adjusts and then return the send status. */
3642-
if (sendRet == WS_WANT_WRITE || sendRet == WS_WINDOW_FULL) {
3643-
int recv2 = DoReceive(ssh);
3644-
if (recv2 == WS_SUCCESS || recv2 == WS_WANT_READ || recv2 == WS_CHAN_RXD)
3645-
ret = sendRet;
3646-
else
3647-
ret = recv2;
3648-
}
3649-
else {
3650-
/* Preserve meaningful receive status when send succeeded. */
3651-
if (sendRet != WS_SUCCESS)
3628+
sendRet = wolfSSH_SendPacket(ssh);
3629+
if (sendRet != WS_SUCCESS) {
3630+
if (ret == WS_SUCCESS) {
36523631
ret = sendRet;
3653-
/* else leave ret as prior receive result (SUCCESS/WANT_READ/CHAN_RXD). */
3632+
}
3633+
else if (rxErr != WS_WANT_READ) {
3634+
/* Only WS_WANT_READ means DoReceive() had nothing to
3635+
* report; anything else outranks wolfSSH_SendPacket(). */
3636+
ssh->error = rxErr;
3637+
}
36543638
}
36553639
}
3656-
#endif /* WOLFSSH_TEST_BLOCK */
36573640

36583641
/* WS_EXTDATA reports the channel too, so a multi-channel caller can route
36593642
* the drain to wolfSSH_ChannelIdReadExt(). */
@@ -4074,8 +4057,8 @@ static int _ChannelRead(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz)
40744057
}
40754058
}
40764059
else {
4077-
/* SendPacket() records only WS_WANT_WRITE, so a hard failure has to
4078-
* be recorded here; rewriting WS_WANT_WRITE is deliberate. */
4060+
/* The window adjust failed. ssh->error carries the code, and the
4061+
* log skips a WS_WANT_WRITE, which only asks for a retry. */
40794062
ssh->error = updateResult;
40804063
if (updateResult != WS_WANT_WRITE) {
40814064
WLOG(WS_LOG_ERROR,
@@ -4135,8 +4118,8 @@ static int _ChannelReadExt(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz)
41354118
ssh->error = savedError;
41364119
}
41374120
else {
4138-
/* SendPacket() sets ssh->error only for WS_WANT_WRITE, so hard
4139-
* failures must be recorded here or they stay hidden. */
4121+
/* The window adjust failed. ssh->error carries the code, and
4122+
* the log skips a WS_WANT_WRITE, which only asks for a retry. */
41404123
ssh->error = adjustResult;
41414124
if (adjustResult != WS_WANT_WRITE) {
41424125
WLOG(WS_LOG_ERROR,

tests/regress.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4499,11 +4499,19 @@ static void TestWorkerReportsDisconnect(void)
44994499
wolfSSH_SetIOReadCtx(ssh, &io);
45004500
wolfSSH_SetIOWriteCtx(ssh, &io);
45014501

4502+
/* Queued output, so the flush on this pass has something to push. */
4503+
ssh->outputBuffer.length = 1;
4504+
ssh->outputBuffer.idx = 0;
4505+
ssh->outputBuffer.buffer[0] = 0;
4506+
45024507
AssertIntEQ(wolfSSH_worker(ssh, NULL), WS_FATAL_ERROR);
45034508
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
45044509
AssertTrue(ssh->disconnected);
45054510
AssertTrue(ssh->isKeying != 0);
4506-
io.outSz = 0;
4511+
4512+
/* The queued byte stays put: the session ended on this very pass. */
4513+
AssertIntEQ(io.outSz, 0);
4514+
AssertTrue(wolfSSH_OutputPending(ssh));
45074515

45084516
/* The message behind it is still queued, and every further pass reports
45094517
* the disconnect rather than the WS_SUCCESS of a skipped dispatch or the

0 commit comments

Comments
 (0)