Commit 7f850c3dbb for openssl.org

commit 7f850c3dbb2b388441fea5b55eed8dc20f5cab5e
Author: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Date:   Sat Jul 4 12:42:36 2026 +0200

    statem: test tls_construct_end_of_early_data

    Cover the success path (state advances to FINISHED_WRITING, empty body)
    and the invalid-state error path.

    Assisted-by: Claude:claude-opus-4-8

    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
    MergeDate: Mon Jul 20 09:11:47 2026
    (Merged from https://github.com/openssl/openssl/pull/31861)

diff --git a/test/statem_clnt_construct_test.c b/test/statem_clnt_construct_test.c
index 985e30cb6a..debedb3af1 100644
--- a/test/statem_clnt_construct_test.c
+++ b/test/statem_clnt_construct_test.c
@@ -678,6 +678,71 @@ static int mfail_construct_ch_ech(void)
 }
 #endif /* OSSL_NO_USABLE_ECH */

+/*
+ * ===========================================================================
+ * tls_construct_end_of_early_data
+ * ===========================================================================
+ */
+
+#ifndef OSSL_NO_USABLE_TLS1_3
+/*
+ * EndOfEarlyData carries no body and only advances early_data_state; it is
+ * valid only from the WRITE_RETRY/FINISHED_WRITING states.
+ */
+static int do_construct_eoed(int state, CON_FUNC_RETURN expect)
+{
+    CH_CONFIG cfg = { 0, TLS1_3_VERSION, TLS1_3_VERSION, 0 };
+    SSL_CTX *cctx = NULL;
+    SSL *ssl = NULL;
+    SSL_CONNECTION *s;
+    WPACKET pkt;
+    unsigned char *msg = NULL;
+    size_t msglen = 0;
+    int have_pkt = 0;
+    int ret = 0;
+
+    if (!TEST_ptr(cctx = new_ctx(&cfg, client_method(&cfg)))
+        || !TEST_ptr(ssl = SSL_new(cctx))
+        || !prime_ssl(ssl, 1, 0, &pkt, SSL3_MT_END_OF_EARLY_DATA))
+        goto err;
+    have_pkt = 1;
+    s = SSL_CONNECTION_FROM_SSL(ssl);
+    s->early_data_state = state;
+
+    if (!TEST_int_eq(tls_construct_end_of_early_data(s, &pkt), expect))
+        goto err;
+
+    if (expect == CON_FUNC_SUCCESS) {
+        /* State advances and the body is empty (only the header is written). */
+        if (!TEST_int_eq(s->early_data_state, SSL_EARLY_DATA_FINISHED_WRITING)
+            || !finish_msg(ssl, &pkt, SSL3_MT_END_OF_EARLY_DATA, &msg, &msglen))
+            goto err;
+        have_pkt = 0;
+        if (!TEST_size_t_eq(msglen, hdr_len(&cfg)))
+            goto err;
+    }
+
+    ret = 1;
+err:
+    if (have_pkt)
+        WPACKET_cleanup(&pkt);
+    SSL_free(ssl);
+    SSL_CTX_free(cctx);
+    return ret;
+}
+
+static int test_construct_eoed(void)
+{
+    return do_construct_eoed(SSL_EARLY_DATA_WRITE_RETRY, CON_FUNC_SUCCESS);
+}
+
+static int test_construct_eoed_bad_state(void)
+{
+    /* Called from an unexpected state: CON_FUNC_ERROR, nothing written. */
+    return do_construct_eoed(SSL_EARLY_DATA_NONE, CON_FUNC_ERROR);
+}
+#endif /* OSSL_NO_USABLE_TLS1_3 */
+
 int setup_tests(void)
 {
     ADD_TEST(test_construct_ch_small_buf);
@@ -693,10 +758,8 @@ int setup_tests(void)
     ADD_TEST(test_construct_ch_tls13);
     ADD_TEST(test_construct_ch_tls13_no_middlebox);
     ADD_TEST(test_construct_ch_hrr);
-    /*
-     * The non-cached mfail run takes too long and does not test too much extra
-     * so better to skip it.
-     */
+    ADD_TEST(test_construct_eoed);
+    ADD_TEST(test_construct_eoed_bad_state);
 #if defined(OPENSSL_NO_ECX)
     /*
      * Without ECX the key_share falls back to EC keygen, which makes a