/* * test_netio_close_propagation.c — bug-648: a peer close/EOF must propagate as a * negative error, never as rc==0. * * io_fsm_do() moved the read FSM to FSM_STATE_ERROR on a peer close but returned * rc = -errno, which on a clean EOF (errno==0) is 0. handle_input() then read * rc>=0, never called handle_error, and a blocked cbuf_get() reply-waiter was * never woken. io_classify_io_error() is the extracted, pure decision; it must * return <0 for every close/error case and 0 only for retriable would-block. */ #define _GNU_SOURCE #include #include #include #include /* Pure classifier defined (non-static) in netio.c, linked into this binary. */ extern int io_classify_io_error(int ssl_err, int ecode, int io_rc); /* WANT_READ / WANT_WRITE are retriable would-block → rc==0, FSM stays put. */ static void test_wantread_writeready_is_retriable_zero(void) { TEST_ASSERT_EQUAL_INT(0, io_classify_io_error(SSL_ERROR_WANT_READ, 0, -1)); TEST_ASSERT_EQUAL_INT(0, io_classify_io_error(SSL_ERROR_WANT_WRITE, 0, -1)); printf("test_wantread_writeready_is_retriable_zero: PASS\n"); } /* Non-blocking EAGAIN/EWOULDBLOCK syscall (io_rc<0) is retriable → rc==0. */ static void test_syscall_eagain_is_retriable_zero(void) { TEST_ASSERT_EQUAL_INT(0, io_classify_io_error(SSL_ERROR_SYSCALL, EAGAIN, -1)); TEST_ASSERT_EQUAL_INT(0, io_classify_io_error(SSL_ERROR_SYSCALL, EWOULDBLOCK, -1)); printf("test_syscall_eagain_is_retriable_zero: PASS\n"); } /* * THE BUG: a clean EOF — SSL_read returns 0 with errno==0 — must yield a * NEGATIVE error (not 0) so the close propagates and the waiter is woken. */ static void test_clean_eof_is_negative_not_zero(void) { int rc = io_classify_io_error(SSL_ERROR_SYSCALL, 0 /*errno*/, 0 /*io_rc EOF*/); TEST_ASSERT_TRUE(rc < 0); TEST_ASSERT_EQUAL_INT(-ECONNRESET, rc); printf("test_clean_eof_is_negative_not_zero: PASS\n"); } /* Clean TLS close_notify (ZERO_RETURN) with errno==0 must also be negative. */ static void test_zero_return_is_negative_not_zero(void) { int rc = io_classify_io_error(SSL_ERROR_ZERO_RETURN, 0, 0); TEST_ASSERT_TRUE(rc < 0); TEST_ASSERT_EQUAL_INT(-ECONNRESET, rc); printf("test_zero_return_is_negative_not_zero: PASS\n"); } /* A hard syscall error preserves its real errno (still negative). */ static void test_hard_syscall_error_preserves_errno(void) { TEST_ASSERT_EQUAL_INT(-ECONNRESET, io_classify_io_error(SSL_ERROR_SYSCALL, ECONNRESET, -1)); printf("test_hard_syscall_error_preserves_errno: PASS\n"); } void test_netio_close_propagation(void) { printf("\n>> netio peer-close propagation tests\n\n"); RUN_TEST(test_wantread_writeready_is_retriable_zero); RUN_TEST(test_syscall_eagain_is_retriable_zero); RUN_TEST(test_clean_eof_is_negative_not_zero); RUN_TEST(test_zero_return_is_negative_not_zero); RUN_TEST(test_hard_syscall_error_preserves_errno); }