@@ -2234,7 +2234,9 @@ TEST(publish_qos2_v5_pubrec_rejection_multithread_reader)
22342234 /* The publisher's own struct is NOT updated on this path. */
22352235 ASSERT_EQ (MQTT_REASON_SUCCESS , publish .resp .reason_code );
22362236}
2237+ #endif /* WOLFMQTT_MULTITHREAD && WOLFMQTT_NONBLOCK */
22372238
2239+ #ifdef WOLFMQTT_MULTITHREAD
22382240/* [MQTT-3.3.4-9] Receive Maximum bounds unacknowledged QoS>0 PUBLISH packets,
22392241 * and MqttClient_Publish_WriteOnly must honor it like any other publish. With
22402242 * the quota exhausted, a write-only QoS 1 publish must be refused before
@@ -2298,9 +2300,13 @@ TEST(publish_writeonly_v5_receive_max_released_on_puback)
22982300 publish .total_len = (word32 )(sizeof (payload ) - 1 );
22992301 publish .buffer_len = publish .total_len ;
23002302
2301- rc = MqttClient_Publish_WriteOnly (& test_client , & publish , NULL );
2302- /* The single unit was reserved and the call returned without waiting. */
2303- ASSERT_EQ (MQTT_CODE_CONTINUE , rc );
2303+ /* Drive the write-only publish to completion. It returns CONTINUE under
2304+ * WOLFMQTT_NONBLOCK and SUCCESS in a blocking MT build; either way the unit
2305+ * is reserved and outstanding until the reader processes the ack. */
2306+ rc = MQTT_CODE_CONTINUE ;
2307+ for (i = 0 ; i < 20 && rc == MQTT_CODE_CONTINUE ; i ++ ) {
2308+ rc = MqttClient_Publish_WriteOnly (& test_client , & publish , NULL );
2309+ }
23042310 ASSERT_EQ (0 , test_client .server_recv_max );
23052311
23062312 /* Reading thread processes the PUBACK. */
@@ -2315,7 +2321,55 @@ TEST(publish_writeonly_v5_receive_max_released_on_puback)
23152321 /* The reserved unit was credited back rather than leaked. */
23162322 ASSERT_EQ (1 , test_client .server_recv_max );
23172323}
2318- #endif /* WOLFMQTT_MULTITHREAD && WOLFMQTT_NONBLOCK */
2324+
2325+ /* A write-only QoS 2 publish reserves a unit; a broker rejection at the PUBREC
2326+ * stage (reason >= 0x80) ends the exchange with no PUBCOMP, so the unit must
2327+ * still be credited back rather than leaked. */
2328+ TEST (publish_writeonly_v5_receive_max_released_on_pubrec_reject )
2329+ {
2330+ int rc ;
2331+ int i ;
2332+ static MqttPublish publish ;
2333+ static byte payload [] = "hello" ;
2334+ /* v5 PUBREC: type=0x50, remain=3, packet_id=22 (0x16), reason=0x97. */
2335+ static const byte pubrec [] = { 0x50 , 0x03 , 0x00 , 0x16 , 0x97 };
2336+
2337+ rc = test_init_client ();
2338+ ASSERT_EQ (MQTT_CODE_SUCCESS , rc );
2339+ test_client .protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5 ;
2340+ test_client .server_recv_max = 1 ;
2341+ test_client .server_recv_max_negotiated = 1 ;
2342+
2343+ test_net .write = mock_net_write_accept ;
2344+ test_net .read = mock_net_read_canned ;
2345+
2346+ XMEMSET (& publish , 0 , sizeof (publish ));
2347+ publish .qos = MQTT_QOS_2 ;
2348+ publish .packet_id = 22 ;
2349+ publish .topic_name = "test/topic" ;
2350+ publish .buffer = payload ;
2351+ publish .total_len = (word32 )(sizeof (payload ) - 1 );
2352+ publish .buffer_len = publish .total_len ;
2353+
2354+ rc = MQTT_CODE_CONTINUE ;
2355+ for (i = 0 ; i < 20 && rc == MQTT_CODE_CONTINUE ; i ++ ) {
2356+ rc = MqttClient_Publish_WriteOnly (& test_client , & publish , NULL );
2357+ }
2358+ ASSERT_EQ (0 , test_client .server_recv_max );
2359+
2360+ /* Reading thread processes the rejecting PUBREC. */
2361+ XMEMCPY (g_canned_buf , pubrec , sizeof (pubrec ));
2362+ g_canned_len = (int )sizeof (pubrec );
2363+ g_canned_pos = 0 ;
2364+ rc = MQTT_CODE_CONTINUE ;
2365+ for (i = 0 ; i < 20 && rc == MQTT_CODE_CONTINUE ; i ++ ) {
2366+ rc = MqttClient_WaitMessage (& test_client , TEST_CMD_TIMEOUT_MS );
2367+ }
2368+
2369+ /* The reserved unit was credited back despite the rejection. */
2370+ ASSERT_EQ (1 , test_client .server_recv_max );
2371+ }
2372+ #endif /* WOLFMQTT_MULTITHREAD */
23192373#endif /* WOLFMQTT_V5 */
23202374
23212375/* Regression test for MQTT Packet Identifier in-use collision check. The
@@ -3597,8 +3651,11 @@ void run_mqtt_client_tests(void)
35973651 RUN_TEST (publish_qos1_v5_write_failure_restores_recv_quota );
35983652#if defined(WOLFMQTT_MULTITHREAD ) && defined(WOLFMQTT_NONBLOCK )
35993653 RUN_TEST (publish_qos2_v5_pubrec_rejection_multithread_reader );
3654+ #endif
3655+ #ifdef WOLFMQTT_MULTITHREAD
36003656 RUN_TEST (publish_writeonly_v5_receive_max_quota_exhausted_rejects );
36013657 RUN_TEST (publish_writeonly_v5_receive_max_released_on_puback );
3658+ RUN_TEST (publish_writeonly_v5_receive_max_released_on_pubrec_reject );
36023659#endif
36033660#endif
36043661#if defined(WOLFMQTT_MULTITHREAD ) && defined(WOLFMQTT_NONBLOCK )
0 commit comments