/*
 * Copyright 2025-2026 The OpenSSL Project Authors. All Rights Reserved.
 *
 * Licensed under the Apache License 2.0 (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
 */

#include <string.h>
#include "helpers/ssltestlib.h"
#include "testutil.h"
#include <openssl/ech.h>
#include <internal/ech_helpers.h>
#include <internal/packet.h>

#define OSSL_ECH_MAX_LINELEN 1000 /* for a sanity check */
#define DEF_CERTS_DIR "test/certs"

/* the testcase numbers */
#define TESTCASE_CH 1
#define TESTCASE_SH 2
#define TESTCASE_ECH 3

static OSSL_LIB_CTX *libctx = NULL;
static char *propq = NULL;
static OSSL_ECHSTORE *es = NULL;
static OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT;
static int verbose = 0;
static int testcase = 0;
static int testiter = 0;
static char *certsdir = NULL;
static char *cert = NULL;
static char *privkey = NULL;
static unsigned char *hpke_info = NULL;
static size_t hpke_infolen = 0;
static int short_test = 0;

/*
 * An x25519 ech key and ECHConfigList with public name example.com
 * and the associated base64 encoded and binary forms of that
 * ECHConfigList - hardcoding here is ok as we're testing for
 * effects of corrupted CH/SH and not for ECHConfig badness.
 */
static const char pem_kp1[] = "-----BEGIN PRIVATE KEY-----\n"
                              "MC4CAQAwBQYDK2VuBCIEILDIeo9Eqc4K9/uQ0PNAyMaP60qrxiSHT2tNZL3ksIZS\n"
                              "-----END PRIVATE KEY-----\n"
                              "-----BEGIN ECHCONFIG-----\n"
                              "AD7+DQA6bAAgACCY7B0f/3KvHIFdoqFaObdU8YYU+MdBf4vzbLhAAL2QCwAEAAEA\n"
                              "AQALZXhhbXBsZS5jb20AAA==\n"
                              "-----END ECHCONFIG-----\n";
static const char echconfig[] = "AD7+DQA6bAAgACCY7B0f/3KvHIFdoqFaObdU8YYU+MdBf4vzbLhAAL2QCwAEAAEA"
                                "AQALZXhhbXBsZS5jb20AAA==";
static size_t echconfiglen = sizeof(echconfig) - 1;

/* a second ECHConfig for when we want to use the wrong one */
static const char ec_kp2[] = "AEf+DQBDvQAgACCr9pErR7E/gNeoni+0YpDZaMd7XN+hFnCN+H0Xnm1EHQAEAAEAAQAUZnJvbnQuc2VydmVyLmV4YW1wbGUAAA==";
static size_t ec_kp2len = sizeof(ec_kp2) - 1;

static unsigned char bin_echconfig[] = {
    0x00, 0x3e, 0xfe, 0x0d, 0x00, 0x3a, 0x6c, 0x00,
    0x20, 0x00, 0x20, 0x98, 0xec, 0x1d, 0x1f, 0xff,
    0x72, 0xaf, 0x1c, 0x81, 0x5d, 0xa2, 0xa1, 0x5a,
    0x39, 0xb7, 0x54, 0xf1, 0x86, 0x14, 0xf8, 0xc7,
    0x41, 0x7f, 0x8b, 0xf3, 0x6c, 0xb8, 0x40, 0x00,
    0xbd, 0x90, 0x0b, 0x00, 0x04, 0x00, 0x01, 0x00,
    0x01, 0x00, 0x0b, 0x65, 0x78, 0x61, 0x6d, 0x70,
    0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x00
};
static size_t bin_echconfiglen = sizeof(bin_echconfig);

/*
 * We can grab the CH and SH and manipulate those to check good
 * behaviour in the face of various errors. The most important
 * thing to test is the server processing of the new combinations
 * that result from the EncodedInnerClientHello (basically the raw
 * output of ECH decryption). We test that via test vectors for
 * those various borked values that we encrypt (via HPKE) and
 * inject into the CH. The SH is much simpler since there are
 * far fewer things to test with the magic encoding of the ECH
 * accept signal into the SH.random or HRR.extension, but we
 * can also test with borked versions of those.
 *
 * We'd like to, but so far cannot, do similarly for the ECH
 * retry-config in EncryptedExtensions. Seems like there's no
 * good way to get at the plaintext there and replace it with
 * a borked value. (QUIC tests seem to have a way to do that
 * but I've yet to figure how to replicate that here for the
 * retry-config.)
 */

/*
 * For client hello, we use a set of test vectors for each test:
 *  - encoded inner CH prefix
 *  - encoded inner CH for borking (esp. outer extensions)
 *  - encoded inner CH postfix
 *  - expected result (1 for good, 0 for bad)
 *  - expected error reason in the case of bad
 *
 * For each test, we replace the ECH ciphertext with a value
 * that's the HPKE seal/enc of an encoded inner-CH made up of
 * the three parts above and then see if we get the expected
 * error (reason).
 *
 * Whenever we re-seal we will get an error due to using the
 * wrong inner client random, which we don't know. But that
 * differs from errors in handling decoding after decryption.
 *
 * The inner CH is split in 3 variables so we can re-use pre
 * and post values, making it easier to understand/manipulate
 * a corrupted-or-not value.
 *
 * Note that the overall length of the encoded inner needs to
 * be maintained as otherwise outer length fields that are not
 * re-computed will be wrong. (We include a test of that as
 * well.) A radical change in the content of encoded inner
 * values (e.g. eliminating compression entirely) could break
 * these tests, but minor changes should have no effect due to
 * padding. (Such a radical change showing up as a fail of
 * these tests is arguably a good outcome.)
 */
typedef struct {
    const unsigned char *pre;
    size_t prelen;
    const unsigned char *forbork;
    size_t fblen;
    const unsigned char *post;
    size_t postlen;
    int rv_expected; /* expected result */
    int err_expected; /* expected error */
} TEST_ECHINNER;

/* a full padded, encoded inner client hello */
static const unsigned char entire_encoded_inner[] = {
    0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
    0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
    0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
    0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
    0x96, 0xc9, 0x00, 0x00, 0x08, 0x13, 0x02, 0x13,
    0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00,
    0x32, 0xfd, 0x00, 0x00, 0x11, 0x10,
    0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33,
    0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
    0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
    0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
    0xfe, 0x0d, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00
};

/* a full padded, encoded inner client hello with no extensions */
static const unsigned char no_ext_encoded_inner[] = {
    0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
    0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
    0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
    0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
    0x96, 0xc9, 0x00, 0x00, 0x08, 0x13, 0x02, 0x13,
    0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

/* a too-short, encoded inner client hello */
static const unsigned char outer_short_encoded_inner[] = {
    0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
    0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
    0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
    0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
    0x96, 0xc9, 0x00, 0x00, 0x08, 0x13, 0x02, 0x13,
    0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00,
    0x32, 0xfd, 0x00, 0x00, 0x11, 0x10,
    0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33,
    0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
    0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
    0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
    0xfe, 0x0d, 0x00, 0x01, 0x01, 0x00, 0x00
};

/* inner prefix up as far as outer_exts */
static const unsigned char encoded_inner_pre[] = {
    0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
    0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
    0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
    0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
    0x96, 0xc9, 0x00, 0x00, 0x08, 0x13, 0x02, 0x13,
    0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00,
    0x32
};

/* inner prefix with mad length of suites (0xDDDD) */
static const unsigned char badsuites_inner_pre[] = {
    0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
    0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
    0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
    0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
    0x96, 0xc9, 0x00, 0xDD, 0xDD, 0x13, 0x02, 0x13,
    0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00,
    0x32
};

/* outer extensions - we play with variations of this */
static const unsigned char encoded_inner_outers[] = {
    0xfd, 0x00, 0x00, 0x11, 0x10,
    0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
};

/* outers with repetition of one extension (0x23) */
static const unsigned char borked_outer1[] = {
    0xfd, 0x00, 0x00, 0x11, 0x10,
    0x00, 0x23, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
};

/* outers including a non-used extension (0xFFAB) */
static const unsigned char borked_outer2[] = {
    0xfd, 0x00, 0x00, 0x11, 0x10,
    0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0xFF, 0xAB, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
};

/* refer to SNI in outers! 2nd-last is 0x0000 */
static const unsigned char borked_outer3[] = {
    0xfd, 0x00, 0x00, 0x11, 0x10,
    0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0x00, 0x0d, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x33
};

/* refer to ECH (0xfe0d) within outers */
static const unsigned char borked_outer4[] = {
    0xfd, 0x00, 0x00, 0x11, 0x10,
    0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0xFE, 0x0D, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
};

/* refer to outers (0xfd00) within outers */
static const unsigned char borked_outer5[] = {
    0xfd, 0x00, 0x00, 0x11, 0x10,
    0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0xFD, 0x00, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
};

/* no outers at all! include unknown ext 0xFF99 instead */
static const unsigned char borked_outer6[] = {
    0xFF, 0x99, 0x00, 0x11, 0x10,
    0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
};

/*
 * outer with bad length (even number of octets)
 * we add a short bogus extension (0xFFFF) after
 * to ensure overall decode succeeds
 */
static const unsigned char borked_outer7[] = {
    0xfd, 0x00, 0x00, 0x0E, 0x10,
    0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0x00, 0x0d, 0x00, 0xFF, 0xFF, 0x00, 0x01, 0x00
};

/* outer with bad inner length (odd number of octets)  */
static const unsigned char borked_outer8[] = {
    0xfd, 0x00, 0x00, 0x11, 0x11,
    0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
};

/* outer with HUGE length (0xFF11) */
static const unsigned char borked_outer9[] = {
    0xfd, 0x00, 0xFF, 0x11, 0x10,
    0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
};

/* outer with zero length, followed by bogus ext */
static const unsigned char borked_outer10[] = {
    0xfd, 0x00, 0x00, 0x00, 0xFF,
    0x0F, 0x00, 0x0D, 0x23, 0x00, 0x16, 0x00, 0x17,
    0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
};

/* refer to key-share 0x00 0x33 (51) twice within outers */
static const unsigned char borked_outer11[] = {
    0xfd, 0x00, 0x00, 0x11, 0x10,
    0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0x00, 0x33, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
};

/* refer to psk kex mode (0x00 0x2D/45) within outers */
static const unsigned char borked_outer12[] = {
    0xfd, 0x00, 0x00, 0x11, 0x10,
    0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0x00, 0x2D, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
};

static const unsigned char encoded_inner_post[] = {
    0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
    0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
    0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
    0xfe, 0x0d, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00
};

/* muck up the padding by including non-zero stuff */
static const unsigned char bad_pad_encoded_inner_post[] = {
    0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
    0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
    0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
    0xfe, 0x0d, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00
};

/* an encoded inner that's just too short */
static const unsigned char short_encoded_inner[] = {
    0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

/*
 * too many outer extensions - max is 20 (decimal)
 * defined as OSSL_ECH_OUTERS_MAX
 */
static const unsigned char too_many_outers[] = {
    0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
    0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
    0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
    0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
    0x96, 0xc9, 0x00, 0x00, 0x08, 0x13, 0x02, 0x13,
    0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00,
    0x00, 0x4c, /* extslen, incl. our added outers */
    0xfd, 0x00, /* outers */
    0x00, 0x2b, /* len of outers */
    0x2a, /* above minus one (42) 21 outers */
    0x00, 0x0a, /* the 8 'normal' outers */
    0x00, 0x23,
    0x00, 0x16,
    0x00, 0x17,
    0x00, 0x0d,
    0x00, 0x2b,
    0x00, 0x2d,
    0x00, 0x33,
    0x00, 0x0b, /* point encoding, not actually in outer */
    /* 12 more outers, set 'em all to ALPN (16, 0x10) */
    0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10,
    0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10,
    0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10,
    /* and now the inner SNI, inner ECH and 3 padding octets */
    0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
    0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
    0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
    0xfe, 0x0d, 0x00, 0x01, 0x01,
    0x00, 0x00, 0x00
};

static const unsigned char tlsv12_like_inner[] = {
    0x03, 0x03, /* version, then client-random */
    0x23, 0xc3, 0xa0, 0x49, 0xea, 0x17, 0x9e, 0x30,
    0x6f, 0x0e, 0xc9, 0x79, 0xd0, 0xd1, 0xfd, 0xea,
    0x63, 0xfd, 0x20, 0x04, 0xaa, 0xb3, 0x2a, 0x29,
    0xf5, 0x96, 0x60, 0x29, 0x42, 0x7e, 0x5c, 0x7b,
    0x00, /* zero'd session ID */
    0x00, 0x02, /* ciphersuite len, just one */
    0xc0, 0x2c, /* a TLSv1.2 ciphersuite */
    0x01, 0x00, /* no compression */
    0x00, 0x2c, /* extslen */
    0xfd, 0x00, /* outers */
    0x00, 0x0b, /* len of outers */
    0x0a, /* above minus one (10) 5 outers */
    0x00, 0x0a, /* the 'normal' outers, minus supported_versions */
    0x00, 0x23,
    0x00, 0x16,
    0x00, 0x17,
    0x00, 0x0d,
    /* and now the inner SNI, inner ECH and padding octets */
    0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
    0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
    0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
    0xfe, 0x0d, 0x00, 0x01, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00
};

/*
 * a full padded, encoded inner client hello, but
 * without an inner supported extensions, (take
 * out the 0x00 0x2b and add some padding zeros,
 * adjusting lengths) and hence meaning TLSv1.2
 */
static const unsigned char no_supported_exts[] = {
    0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
    0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
    0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
    0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
    0x96, 0xc9, 0x00, 0x00, 0x08, 0x13, 0x02, 0x13,
    0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00,
    0x30, 0xfd, 0x00, 0x00, 0x0f, 0x0e,
    0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0x00, 0x0d, 0x00, 0x2d, 0x00, 0x33,
    0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
    0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
    0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
    0xfe, 0x0d, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00
};

static const unsigned char tlsv12_inner[] = {
    0x03, 0x03, /* version, then client-random */
    0x23, 0xc3, 0xa0, 0x49, 0xea, 0x17, 0x9e, 0x30,
    0x6f, 0x0e, 0xc9, 0x79, 0xd0, 0xd1, 0xfd, 0xea,
    0x63, 0xfd, 0x20, 0x04, 0xaa, 0xb3, 0x2a, 0x29,
    0xf5, 0x96, 0x60, 0x29, 0x42, 0x7e, 0x5c, 0x7b,
    0x00, /* zero'd session ID */
    0x00, 0x02, /* ciphersuite len, just one */
    0xc0, 0x2c, /* a TLSv1.2 ciphersuite */
    0x01, 0x00, /* no compression */
    0x00, 0x32, /* extslen */
    0xfd, 0x00, /* outers */
    0x00, 0x10, /* len of outers */
    0x0e, /* above minus one (16) 8 outers */
    0x00, 0x0a, /* the 'normal' outers, minus supported_versions */
    0x00, 0x23,
    0x00, 0x16,
    0x00, 0x17,
    0x00, 0x0d,
    0x00, 0x2d,
    0x00, 0x33,
    /* and now the inner SNI, inner ECH and padding octets */
    0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
    0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
    0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
    0xfe, 0x0d, 0x00, 0x01, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

/*
 * a full padded, encoded inner with no inner ECH
 * we change 0xfe 0x0d to 0xFF 0xFD in the ext type
 */
static const unsigned char encoded_inner_no_ech[] = {
    0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
    0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
    0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
    0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
    0x96, 0xc9, 0x00, 0x00, 0x08, 0x13, 0x02, 0x13,
    0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00,
    0x32, 0xfd, 0x00, 0x00, 0x11, 0x10,
    0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
    0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33,
    0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
    0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
    0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
    0xFF, 0xFD, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00
};

/* A set of test vectors */
static TEST_ECHINNER test_inners[] = {
    /* 1. basic case - copy to show test code works with no change */
    { NULL, 0, NULL, 0, NULL, 0, 1, SSL_ERROR_NONE },

    /* 2. too-short encoded inner */
    { NULL, 0,
        outer_short_encoded_inner, sizeof(outer_short_encoded_inner),
        NULL, 0,
        0, /* expected result */
        SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC },
    /* 3. otherwise-correct case that fails only due to client random */
    { NULL, 0,
        entire_encoded_inner, sizeof(entire_encoded_inner),
        NULL, 0,
        0, /* expected result */
        SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC },
    /* 4. otherwise-correct case that fails only due to client random */
    { encoded_inner_pre, sizeof(encoded_inner_pre),
        encoded_inner_outers, sizeof(encoded_inner_outers),
        encoded_inner_post, sizeof(encoded_inner_post),
        0, /* expected result */
        SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC },
    /* 5. fails HPKE decryption due to bad padding so treated as GREASE */
    { encoded_inner_pre, sizeof(encoded_inner_pre),
        encoded_inner_outers, sizeof(encoded_inner_outers),
        bad_pad_encoded_inner_post, sizeof(bad_pad_encoded_inner_post),
        0, /* expected result */
        SSL_R_TLS_ALERT_ILLEGAL_PARAMETER },
    /*
     * 6. unsupported extension instead of outers - resulting decoded
     * inner missing so much it seems to be the wrong protocol
     */
    { encoded_inner_pre, sizeof(encoded_inner_pre),
        borked_outer6, sizeof(borked_outer6),
        encoded_inner_post, sizeof(encoded_inner_post),
        0, /* expected result - error is different with -notls1_2 */
#ifdef OPENSSL_NO_TLS1_2
        SSL_R_VERSION_TOO_LOW
#else
        SSL_R_UNSUPPORTED_PROTOCOL
#endif
    },

    /* 7. madly long ciphersuites in inner */
    { badsuites_inner_pre, sizeof(badsuites_inner_pre),
        encoded_inner_outers, sizeof(encoded_inner_outers),
        encoded_inner_post, sizeof(bad_pad_encoded_inner_post),
        0, /* expected result */
        SSL_R_TLSV1_ALERT_DECODE_ERROR },
    /* 8. so many padding bytes recovered clear is short */
    { NULL, 0,
        short_encoded_inner, sizeof(short_encoded_inner),
        NULL, 0,
        0, /* expected result */
        SSL_R_BAD_EXTENSION },

    /* 9. repeated codepoint inside outers */
    { encoded_inner_pre, sizeof(encoded_inner_pre),
        borked_outer1, sizeof(borked_outer1),
        encoded_inner_post, sizeof(encoded_inner_post),
        0, /* expected result */
        SSL_R_BAD_EXTENSION },
    /* 10. non-existent codepoint inside outers */
    { encoded_inner_pre, sizeof(encoded_inner_pre),
        borked_outer2, sizeof(borked_outer2),
        encoded_inner_post, sizeof(encoded_inner_post),
        0, /* expected result */
        SSL_R_BAD_EXTENSION },
    /* 11. include SNI in outers as well as both inner and outer */
    { encoded_inner_pre, sizeof(encoded_inner_pre),
        borked_outer3, sizeof(borked_outer3),
        encoded_inner_post, sizeof(encoded_inner_post),
        0, /* expected result */
        SSL_R_BAD_EXTENSION },
    /* 12. refer to ECH within outers */
    { encoded_inner_pre, sizeof(encoded_inner_pre),
        borked_outer4, sizeof(borked_outer4),
        encoded_inner_post, sizeof(encoded_inner_post),
        0, /* expected result */
        SSL_R_BAD_EXTENSION },
    /* 13. refer to outers within outers */
    { encoded_inner_pre, sizeof(encoded_inner_pre),
        borked_outer5, sizeof(borked_outer5),
        encoded_inner_post, sizeof(encoded_inner_post),
        0, /* expected result */
        SSL_R_BAD_EXTENSION },
    /* 14. bad length of outers */
    { encoded_inner_pre, sizeof(encoded_inner_pre),
        borked_outer7, sizeof(borked_outer7),
        encoded_inner_post, sizeof(encoded_inner_post),
        0, /* expected result */
        SSL_R_BAD_EXTENSION },
    /* 15. bad inner length in outers */
    { encoded_inner_pre, sizeof(encoded_inner_pre),
        borked_outer8, sizeof(borked_outer8),
        encoded_inner_post, sizeof(encoded_inner_post),
        0, /* expected result */
        SSL_R_BAD_EXTENSION },
    /* 16. HUGE length in outers */
    { encoded_inner_pre, sizeof(encoded_inner_pre),
        borked_outer9, sizeof(borked_outer9),
        encoded_inner_post, sizeof(encoded_inner_post),
        0, /* expected result */
        SSL_R_BAD_EXTENSION },
    /* 17. zero length in outers */
    { encoded_inner_pre, sizeof(encoded_inner_pre),
        borked_outer10, sizeof(borked_outer10),
        encoded_inner_post, sizeof(encoded_inner_post),
        0, /* expected result */
        SSL_R_BAD_EXTENSION },
    /* 18. case with no extensions at all */
    { NULL, 0,
        no_ext_encoded_inner, sizeof(no_ext_encoded_inner),
        NULL, 0,
        0, /* expected result */
        SSL_R_BAD_EXTENSION },
    /*
     * 19. include key-share twice in outers as well as both inner and outer.
     * There was a change with this one recently that can/does cause a
     * different error message (used to be SSL_R_BAD_EXTENSION, but now
     * mostly ERR_R_INTERNAL_ERROR). The issue is that this test repeats the
     * key_share in the compressed exts and with PQ kybrid KEMs those are
     * so large that instead of detecting the duplicate extension we see
     * an earlier error where the inner CH is bigger than the outer.
     */
    { encoded_inner_pre, sizeof(encoded_inner_pre),
        borked_outer11, sizeof(borked_outer11),
        encoded_inner_post, sizeof(encoded_inner_post),
        0, /* expected result */
#ifdef OPENSSL_NO_ML_KEM
        SSL_R_BAD_EXTENSION
#else
        ERR_R_INTERNAL_ERROR
#endif
    },
    /* 20. include psk key mode ext in outers as well as both inner and outer */
    { encoded_inner_pre, sizeof(encoded_inner_pre),
        borked_outer12, sizeof(borked_outer12),
        encoded_inner_post, sizeof(encoded_inner_post),
        0, /* expected result */
        SSL_R_BAD_EXTENSION },
    /* 21. too many outers */
    { NULL, 0,
        too_many_outers, sizeof(too_many_outers),
        NULL, 0,
        0, /* expected result */
        SSL_R_BAD_EXTENSION },
    /*
     * 22. no supported_versions hence TLSv1.2, with server set to
     * allow max tlsv1.3
     */
    { NULL, 0,
        tlsv12_like_inner, sizeof(tlsv12_like_inner),
        NULL, 0,
        0, /* expected result */ SSL_R_UNSUPPORTED_PROTOCOL },
    /*
     * 23. no supported_versions hence TLSv1.2, with server set to
     * allow max tlsv1.2
     */
    { NULL, 0,
        no_supported_exts, sizeof(no_supported_exts),
        NULL, 0,
        0, /* expected result */
        SSL_R_NO_PROTOCOLS_AVAILABLE },
    /*
     * 24. no supported_versions hence TLSv1.2, with server set to
     * allow min tlsv1.2
     */
    { NULL, 0,
        tlsv12_like_inner, sizeof(tlsv12_like_inner),
        NULL, 0,
        0, /* expected result */ SSL_R_UNSUPPORTED_PROTOCOL },
    /* 25. smuggled TLSv1.2 CH */
    { NULL, 0,
        tlsv12_inner, sizeof(tlsv12_inner),
        NULL, 0,
        0, /* expected result */ SSL_R_BAD_EXTENSION },
    /* 26. otherwise-correct case that fails due to lack of inner ECH */
    { NULL, 0,
        encoded_inner_no_ech, sizeof(encoded_inner_no_ech),
        NULL, 0,
        0, /* expected result */ SSL_R_ECH_REQUIRED },
};

/*
 * For server hello/HRR, we use a set of test vectors for each test:
 *
 * - borkage encodes what we're breaking and is the OR
 *   of some #define'd OSSL_ECH_BORK_* flags
 * - bork is the value to use instead of the real one (or NULL)
 * - blen is the size of bork
 * - rv_expected is the return value expected for the connection
 * - err_expected is the reason code we expect to see
 */
typedef struct {
    int borkage; /* type of borkage */
    unsigned char *bork; /* borked value */
    size_t blen; /* len(bork) */
    int rv_expected; /* expected result */
    int err_expected; /* expected error */
} TEST_SH;

#define OSSL_ECH_BORK_NONE 0
#define OSSL_ECH_BORK_FLIP 1
#define OSSL_ECH_BORK_HRR (1 << 1)
#define OSSL_ECH_BORK_SHORT_HRR_CONFIRM (1 << 2)
#define OSSL_ECH_BORK_LONG_HRR_CONFIRM (1 << 3)
#define OSSL_ECH_BORK_GREASE (1 << 4)
#define OSSL_ECH_BORK_REPLACE (1 << 5)

/* a truncated ECH, with another bogus ext to match overall length */
static unsigned char shortech[] = {
    0xfe, 0x0d, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
    0xdd, 0xdd, 0x00, 0x00
};

/* a too-long ECH internal length */
static unsigned char longech[] = {
    0xfe, 0x0d, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00,
    0xdd, 0xdd, 0x00, 0x00
};

static TEST_SH test_shs[] = {
    /* 1. no messing about, should succeed */
    { OSSL_ECH_BORK_NONE, NULL, 0, 1, SSL_ERROR_NONE },
    /* 2. trigger HRR but no other borkage */
    { OSSL_ECH_BORK_HRR, NULL, 0, 1, SSL_ERROR_NONE },

    /* 3. GREASE and trigger HRR */
    { OSSL_ECH_BORK_HRR | OSSL_ECH_BORK_GREASE,
        NULL, 0, 1, SSL_ERROR_NONE },

    /* 4. flip bits in SH.random ECH confirmation value */
    { OSSL_ECH_BORK_FLIP, NULL, 0, 0,
        SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC },
    /* 5. flip bits in HRR.exts ECH confirmation value */
    { OSSL_ECH_BORK_HRR | OSSL_ECH_BORK_FLIP,
        NULL, 0, 0, SSL_R_ECH_REQUIRED },
    /* 6. truncate HRR.exts ECH confirmation value */
    { OSSL_ECH_BORK_HRR | OSSL_ECH_BORK_REPLACE,
        shortech, sizeof(shortech), 0, SSL_R_LENGTH_MISMATCH },
    /* 7. too-long HRR.exts ECH confirmation value */
    { OSSL_ECH_BORK_HRR | OSSL_ECH_BORK_REPLACE,
        longech, sizeof(longech), 0, SSL_R_BAD_EXTENSION },

};

/*
 * Test vectors for badly encoded ECH extension values for
 * the outer ClientHelllo. We grab the outbound ClientHello
 * and overwrite these values in the appropriate place. That
 * will always break the TLS connection, even with a correct
 * encoding, as we're breaking the transcript, but we expect
 * decoding to catch these and to get 'bad extension' errors
 * in most cases.
 *
 * Note that the code for these tests could be more terse as
 * declaring a separate buffer for each bad value is quite
 * repetitive, but doing it this way is more readable and more
 * easily varied/extended.
 */

/* an entire correctly encoded ECH (len = 190) */
static unsigned char entire_encoded_ech[] = {
    0xfe, 0x0d, 0x00, 0xba, /* ext type & length */
    0x00, /* outer ECH */
    0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
    0x7c, /* config id */
    0x00, 0x20, /* encap len then encap val */
    0x59, 0x87, 0xbe, 0x13, 0xd0, 0xf1, 0x0e, 0x23,
    0xcb, 0x28, 0x26, 0xc2, 0x88, 0xd0, 0x8f, 0xac,
    0x04, 0x99, 0x54, 0x30, 0xa2, 0x0f, 0xfe, 0x53,
    0xf5, 0xa5, 0x92, 0x01, 0xb1, 0x56, 0xd2, 0x3f,
    0x00, 0x90, /* payload len then payload */
    0x9e, 0xe6, 0xed, 0x1d, 0xe2, 0xef, 0x30, 0xb0,
    0x91, 0x00, 0xdc, 0x90, 0x21, 0x9e, 0x5e, 0x6f,
    0xcb, 0xb9, 0xb3, 0x05, 0xdd, 0xac, 0x97, 0x71,
    0xf0, 0x2d, 0x48, 0xf7, 0x01, 0xf4, 0x68, 0x0c,
    0xb4, 0xbe, 0x78, 0x3c, 0xa3, 0xcb, 0x6a, 0x16,
    0x7a, 0xfc, 0x33, 0xcd, 0x12, 0xf3, 0x00, 0x2f,
    0x3e, 0xaa, 0xef, 0x7c, 0x26, 0xd3, 0x6f, 0x46,
    0x8e, 0xb8, 0x54, 0x4c, 0x6a, 0xc3, 0x85, 0x92,
    0x44, 0xc1, 0xe2, 0x03, 0xfe, 0xfc, 0xca, 0xff,
    0x3b, 0x03, 0x9a, 0xf0, 0xd8, 0xe7, 0x2d, 0xb0,
    0xe3, 0x64, 0x9f, 0xb9, 0x78, 0xd3, 0xca, 0x4c,
    0xa2, 0xdd, 0x1f, 0x68, 0x9a, 0x9b, 0xcc, 0xb9,
    0x79, 0x59, 0xb4, 0xac, 0x4e, 0x7d, 0xce, 0xa3,
    0xc7, 0x23, 0xe6, 0x1c, 0xcd, 0x8d, 0xaa, 0xaa,
    0xdb, 0x21, 0xa1, 0xec, 0xb8, 0xbe, 0x53, 0x60,
    0x4f, 0xf4, 0x0b, 0xef, 0xad, 0x1d, 0x45, 0x62,
    0x65, 0x88, 0xfe, 0x15, 0x47, 0x25, 0x61, 0xa5,
    0x65, 0x7a, 0x17, 0xaa, 0x08, 0x3f, 0xe8, 0xf2
};

/* overall length too much */
static unsigned char too_long_ech[] = {
    0xfe, 0x0d, 0xFF, 0xba /* ext type & length */
};

/* overall length too short */
static unsigned char too_short_ech[] = {
    0xfe, 0x0d, 0x00, 0x00 /* ext type & length */
};

/* no inner/outer value */
static unsigned char no_innerouter_ech[] = {
    0xfe, 0x0d, 0x00, 0x00, /* ext type & length */
    0x00
};

/* ECH inner/outer bad value */
static unsigned char bad_innerouter_ech[] = {
    0xfe, 0x0d, 0x00, 0xba, /* ext type & length */
    0xFF
};

/* too short to get to KDF */
static unsigned char too_short_kdf[] = {
    0xfe, 0x0d, 0x00, 0x02, /* ext type & length */
    0x00,
    0x00, 0x01, 0x00, 0x01 /* cipher suite KDF, AEAD */
};

/* too short to get to AEAD */
static unsigned char too_short_aead[] = {
    0xfe, 0x0d, 0x00, 0x04, /* ext type & length */
    0x00,
    0x00, 0x01, 0x00, 0x01 /* cipher suite KDF, AEAD */
};

/* too short to get to config_id */
static unsigned char too_short_cid[] = {
    0xfe, 0x0d, 0x00, 0x05, /* ext type & length */
    0x00,
    0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
    0x7c
};

/* zero length encap (only ok in HRR) */
static unsigned char zero_encap_len[] = {
    0xfe, 0x0d, 0x00, 0xba, /* ext type & length */
    0x00,
    0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
    0x7c,
    0x00, 0x00
};

/* too short to get to encap_len  */
static unsigned char too_short_encap_len[] = {
    0xfe, 0x0d, 0x00, 0x07, /* ext type & length */
    0x00,
    0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
    0x7c,
    0x00
};

/* too long encap len */
static unsigned char too_long_encap_len[] = {
    0xfe, 0x0d, 0x00, 0xba, /* ext type & length */
    0x00,
    0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
    0x7c,
    0xFF, 0xFF
};

/* bit long encap len (more than extension) */
static unsigned char bit_long_encap_len[] = {
    0xfe, 0x0d, 0x00, 0xba, /* ext type & length */
    0x00,
    0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
    0x7c,
    0x00, 0xFF
};

/* too short to get to payload_len */
static unsigned char too_short_payload_len[] = {
    0xfe, 0x0d, 0x00, 0x29, /* ext type & length */
    0x00,
    0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
    0x7c,
    0x00, 0x20, /* encap len then encap val */
    0x59, 0x87, 0xbe, 0x13, 0xd0, 0xf1, 0x0e, 0x23,
    0xcb, 0x28, 0x26, 0xc2, 0x88, 0xd0, 0x8f, 0xac,
    0x04, 0x99, 0x54, 0x30, 0xa2, 0x0f, 0xfe, 0x53,
    0xf5, 0xa5, 0x92, 0x01, 0xb1, 0x56, 0xd2, 0x3f,
    0x00, 0x90 /* payload len then payload */
};

/* bit long payload_len */
static unsigned char bit_long_payload_len[] = {
    0xfe, 0x0d, 0x00, 0xba, /* ext type & length */
    0x00,
    0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
    0x7c,
    0x00, 0x20, /* encap len then encap val */
    0x59, 0x87, 0xbe, 0x13, 0xd0, 0xf1, 0x0e, 0x23,
    0xcb, 0x28, 0x26, 0xc2, 0x88, 0xd0, 0x8f, 0xac,
    0x04, 0x99, 0x54, 0x30, 0xa2, 0x0f, 0xfe, 0x53,
    0xf5, 0xa5, 0x92, 0x01, 0xb1, 0x56, 0xd2, 0x3f,
    0x00, 0xba /* payload len then payload */
};

/* zero payload_len */
static unsigned char zero_payload_len[] = {
    0xfe, 0x0d, 0x00, 0xba, /* ext type & length */
    0x00,
    0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
    0x7c,
    0x00, 0x20, /* encap len then encap val */
    0x59, 0x87, 0xbe, 0x13, 0xd0, 0xf1, 0x0e, 0x23,
    0xcb, 0x28, 0x26, 0xc2, 0x88, 0xd0, 0x8f, 0xac,
    0x04, 0x99, 0x54, 0x30, 0xa2, 0x0f, 0xfe, 0x53,
    0xf5, 0xa5, 0x92, 0x01, 0xb1, 0x56, 0xd2, 0x3f,
    0x00, 0x00 /* payload len then payload */
};

/*
 * Structure for test vectors for ECH in the outer CH
 *  - value to use to overwrite encoded ECH
 *  - expected result (1 for good, 0 for bad)
 *  - expected error reason in the case of bad
 *
 * For each test, we replace the first |len| octets of the
 * ECH extension in the outer CH with the associated |val|.
 *
 * Note that the overall length of the outer CH needs to
 * be maintained as otherwise outer length fields that are not
 * re-computed will be wrong. (We include a test of that as
 * well.) A radical change in the content of encoded inner
 * values (e.g. eliminating compression entirely) could break
 * these tests, but minor changes should have no effect due to
 * padding. (Such a radical change showing up as a fail of
 * these tests is arguably a good outcome.)
 */
typedef struct {
    const unsigned char *val;
    size_t len;
    int rv_expected; /* expected result */
    int err_expected; /* expected error */
} TEST_ECHOUTER;

static TEST_ECHOUTER test_echs[] = {
    /* 1. basic case - copy to show test code works with no change */
    { NULL, 0, 1, SSL_ERROR_NONE },

    /* 2. good encoding/length but breaks TLS session integrity */
    { entire_encoded_ech, sizeof(entire_encoded_ech),
        0, /* expected result */
        SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC },
    /* 3. ECH length too long */
    { too_long_ech, sizeof(too_long_ech),
        0, /* expected result */ SSL_R_BAD_EXTENSION },
    /* 4. ECH length too short */
    { too_short_ech, sizeof(too_short_ech),
        0, /* expected result */ SSL_R_BAD_EXTENSION },
    /* 5. no inner/outer value */
    { no_innerouter_ech, sizeof(no_innerouter_ech),
        0, /* expected result */ SSL_R_BAD_EXTENSION },
    /* 6. inner/outer bad value */
    { bad_innerouter_ech, sizeof(bad_innerouter_ech),
        0, /* expected result */ SSL_R_BAD_EXTENSION },
    /* 7. too_short_kdf value */
    { too_short_kdf, sizeof(too_short_kdf),
        0, /* expected result */ SSL_R_BAD_EXTENSION },
    /* 8. too_short_aead value */
    { too_short_aead, sizeof(too_short_aead),
        0, /* expected result */ SSL_R_BAD_EXTENSION },
    /* 9. too_short_cid value */
    { too_short_cid, sizeof(too_short_cid),
        0, /* expected result */ SSL_R_BAD_EXTENSION },
    /* 10. zero_encap_len value */
    { zero_encap_len, sizeof(zero_encap_len),
        0, /* expected result */ SSL_R_BAD_EXTENSION },
    /* 11. too_short_encap_len value */
    { too_short_encap_len, sizeof(too_short_encap_len),
        0, /* expected result */ SSL_R_BAD_EXTENSION },
    /* 12. too_long_encap_len value */
    { too_long_encap_len, sizeof(too_long_encap_len),
        0, /* expected result */ SSL_R_BAD_EXTENSION },
    /* 13. bit_long_encap_len value */
    { bit_long_encap_len, sizeof(bit_long_encap_len),
        0, /* expected result */ SSL_R_BAD_EXTENSION },
    /* 14.  too_short_payload_len value */
    { too_short_payload_len, sizeof(too_short_payload_len),
        0, /* expected result */ SSL_R_BAD_EXTENSION },
    /* 15. bit_long_payload_len value */
    { bit_long_payload_len, sizeof(bit_long_payload_len),
        0, /* expected result */ SSL_R_BAD_EXTENSION },
    /* 16. zero_payload_len value */
    { zero_payload_len, sizeof(zero_payload_len),
        0, /* expected result */ SSL_R_BAD_EXTENSION },
};

/*
 * Given a SH (or HRR) find the offsets of the ECH (if any)
 * sh is the SH buffer
 * sh_len is the length of the SH
 * exts points to offset of extensions
 * echoffset points to offset of ECH
 * echtype points to the ext type of the ECH
 * for success, other otherwise
 *
 * Offsets are returned to the type or length field in question.
 * Offsets are set to zero if relevant thing not found.
 *
 * Note: input here is untrusted!
 */
static int ech_get_sh_offsets(const unsigned char *sh,
    size_t sh_len, size_t *exts,
    size_t *echoffset, uint16_t *echtype)
{
    unsigned int elen = 0, etype = 0, pi_tmp = 0;
    const unsigned char *pp_tmp = NULL, *shstart = NULL, *estart = NULL;
    PACKET pkt;
    size_t extlens = 0;
    int done = 0;
#ifdef OSSL_ECH_SUPERVERBOSE
    size_t echlen = 0; /* length of ECH, including type & ECH-internal length */
    size_t sessid_offset = 0;
    size_t sessid_len = 0;
#endif

    if (sh == NULL || sh_len == 0 || exts == NULL || echoffset == NULL
        || echtype == NULL)
        return 0;
    *exts = *echoffset = *echtype = 0;
    if (!PACKET_buf_init(&pkt, sh, sh_len))
        return 0;
    shstart = PACKET_data(&pkt);
    if (!PACKET_get_net_2(&pkt, &pi_tmp))
        return 0;
    /* if we're not TLSv1.2+ then we can bail, but it's not an error */
    if (pi_tmp != TLS1_2_VERSION)
        return 1;
    if (!PACKET_get_bytes(&pkt, &pp_tmp, SSL3_RANDOM_SIZE)
#ifdef OSSL_ECH_SUPERVERBOSE
        || (sessid_offset = PACKET_data(&pkt) - shstart) == 0
#endif
        || !PACKET_get_1(&pkt, &pi_tmp) /* sessid len */
#ifdef OSSL_ECH_SUPERVERBOSE
        || (sessid_len = (size_t)pi_tmp) == 0
#endif
        || !PACKET_get_bytes(&pkt, &pp_tmp, pi_tmp) /* sessid */
        || !PACKET_get_net_2(&pkt, &pi_tmp) /* ciphersuite */
        || !PACKET_get_1(&pkt, &pi_tmp) /* compression */
        || (*exts = PACKET_data(&pkt) - shstart) == 0
        || !PACKET_get_net_2(&pkt, &pi_tmp)) /* len(extensions) */
        return 0;
    extlens = (size_t)pi_tmp;
    if (extlens == 0) /* not an error, in theory */
        return 1;
    estart = PACKET_data(&pkt);
    while (PACKET_remaining(&pkt) > 0
        && (size_t)(PACKET_data(&pkt) - estart) < extlens
        && done < 1) {
        if (!PACKET_get_net_2(&pkt, &etype)
            || !PACKET_get_net_2(&pkt, &elen))
            return 0;
        if (etype == TLSEXT_TYPE_ech) {
            if (elen == 0)
                return 0;
            *echoffset = PACKET_data(&pkt) - shstart - 4;
            *echtype = etype;
#ifdef OSSL_ECH_SUPERVERBOSE
            echlen = elen + 4; /* type and length included */
#endif
            done++;
        }
        if (!PACKET_get_bytes(&pkt, &pp_tmp, elen))
            return 0;
    }
#ifdef OSSL_ECH_SUPERVERBOSE
    OSSL_TRACE_BEGIN(TLS)
    {
        BIO_printf(trc_out, "orig SH/ECH type: %4x\n", *echtype);
    }
    OSSL_TRACE_END(TLS);
    ossl_ech_pbuf("orig SH", (unsigned char *)sh, sh_len);
    ossl_ech_pbuf("orig SH session_id", (unsigned char *)sh + sessid_offset,
        sessid_len);
    ossl_ech_pbuf("orig SH exts", (unsigned char *)sh + *exts, extlens);
    ossl_ech_pbuf("orig SH/ECH ", (unsigned char *)sh + *echoffset, echlen);
#endif
    return 1;
}

/* Do a HPKE seal of a padded encoded inner */
static int seal_encoded_inner(char **out, int *outlen,
    unsigned char *ei, size_t eilen,
    const char *ch, int chlen,
    size_t echoffset, size_t echlen)
{
    int res = 0;
    OSSL_HPKE_CTX *hctx = NULL;
    unsigned char *mypub = NULL;
    static size_t mypublen = 0;
    unsigned char *theirpub = NULL;
    size_t theirpublen = 0;
    unsigned char *ct = NULL;
    size_t ctlen = 0;
    unsigned char *aad = NULL;
    size_t aadlen = 0;
    unsigned char *chout = NULL;
    size_t choutlen = 0;

    hctx = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_BASE, hpke_suite,
        OSSL_HPKE_ROLE_SENDER, NULL, NULL);
    if (!TEST_ptr(hctx))
        goto err;
    mypublen = OSSL_HPKE_get_public_encap_size(hpke_suite);
    if (!TEST_ptr(mypub = OPENSSL_malloc(mypublen)))
        goto err;
    theirpub = bin_echconfig + 11;
    theirpublen = 0x20;
    if (!TEST_true(OSSL_HPKE_encap(hctx, mypub, &mypublen,
            theirpub, theirpublen,
            hpke_info, hpke_infolen)))
        goto err;
    /* form up aad which is entire outer CH: zero's instead of ECH ciphertext */
    choutlen = chlen;
    if (!TEST_ptr(chout = OPENSSL_malloc(choutlen)))
        goto err;
    memcpy(chout, ch, chlen);
    memcpy(chout + echoffset + 12, mypub, mypublen);
    ct = chout + echoffset + 12 + mypublen + 2;
    ctlen = OSSL_HPKE_get_ciphertext_size(hpke_suite, eilen);
    chout[echoffset + 12 + mypublen] = (ctlen >> 8) & 0xff;
    chout[echoffset + 12 + mypublen + 1] = ctlen & 0xff;
    /* the 9 skips the record layer header */
    aad = chout + SSL3_RT_HEADER_LENGTH + SSL3_HM_HEADER_LENGTH;
    aadlen = chlen - (SSL3_RT_HEADER_LENGTH + SSL3_HM_HEADER_LENGTH);
    if (short_test == 0 && ct + ctlen != aad + aadlen) {
        TEST_info("length oddity");
        goto err;
    }
    memset(ct, 0, ctlen);
    if (!TEST_true(OSSL_HPKE_seal(hctx, ct, &ctlen, aad, aadlen, ei, eilen)))
        goto err;
    *out = (char *)chout;
    *outlen = (int)choutlen;
    res = 1;
err:
    OPENSSL_free(mypub);
    OSSL_HPKE_CTX_free(hctx);
    return res;
}

/* We'll either corrupt or copy the message based on the test index */
static int corrupt_or_copy(const char *msg, const int msglen,
    char **msgout, int *msgoutlen)
{
    TEST_ECHINNER *ti = NULL;
    TEST_SH *ts = NULL;
    TEST_ECHOUTER *to = NULL;
    int is_ch = 0, is_sh = 0;
    unsigned char *encoded_inner = NULL;
    size_t prelen, fblen, postlen;
    size_t encoded_innerlen = 0;
    size_t sessid = 0, exts = 0, extlens = 0, echoffset = 0, echlen = 0;
    size_t snioffset = 0, snilen = 0;
    uint16_t echtype;
    int inner, rv = 0;

    /* is it a ClientHello or not? */
    if (testcase == TESTCASE_CH && msglen > 10 && msg[0] == SSL3_RT_HANDSHAKE
        && msg[5] == SSL3_MT_CLIENT_HELLO)
        is_ch = 1;
    /* is it a ServerHello or not? */
    if (testcase == TESTCASE_SH && msglen > 10 && msg[0] == SSL3_RT_HANDSHAKE
        && msg[5] == SSL3_MT_SERVER_HELLO)
        is_sh = 1;
    if (testcase == TESTCASE_ECH && msglen > 10 && msg[0] == SSL3_RT_HANDSHAKE
        && msg[5] == SSL3_MT_CLIENT_HELLO)
        is_ch = 1;

    if (testcase == TESTCASE_CH && is_ch == 1) {
        if (testiter >= (int)OSSL_NELEM(test_inners))
            return 0;
        ti = &test_inners[testiter];
        prelen = ti->pre == NULL ? 0 : ti->prelen;
        fblen = ti->forbork == NULL ? 0 : ti->fblen;
        postlen = ti->post == NULL ? 0 : ti->postlen;
        /* check for editing errors */
        if (testiter != 0 && testiter != 1
            && prelen + fblen + postlen != sizeof(entire_encoded_inner)) {
            TEST_info("manual sizing error");
            return 0;
        }
        if (testiter == 1) /* the only case with a short ciphertext for now */
            short_test = 1;
        if (!TEST_true(ossl_ech_helper_get_ch_offsets((const unsigned char *)msg
                    + SSL3_RT_HEADER_LENGTH
                    + SSL3_HM_HEADER_LENGTH,
                msglen
                    - SSL3_RT_HEADER_LENGTH
                    - SSL3_HM_HEADER_LENGTH,
                &sessid, &exts, &extlens,
                &echoffset, &echtype, &echlen,
                &snioffset, &snilen, &inner)))
            return 0;
        /* that better be an outer ECH :-) */
        if (echoffset > 0 && !TEST_int_eq(inner, 0)) {
            TEST_info("better send outer");
            return 0;
        }
        /* bump offsets by 9 */
        echoffset += 9;
        snioffset += 9;
        /*
         * if it doesn't have an ECH, or if the forbork value in our test
         * array is NULL, just copy the entire input to output
         */
        if (echoffset == 9 || ti->forbork == NULL) {
            if (!TEST_ptr(*msgout = OPENSSL_memdup(msg, msglen)))
                return 0;
            *msgoutlen = msglen;
            return 1;
        }
        /* in this case, construct the encoded inner, then seal that */
        encoded_innerlen = prelen + fblen + postlen;
        if (!TEST_ptr(encoded_inner = OPENSSL_malloc(encoded_innerlen)))
            return 0;
        if (ti->pre != NULL) /* keep fuzz checker happy */
            memcpy(encoded_inner, ti->pre, prelen);
        if (ti->forbork != NULL)
            memcpy(encoded_inner + prelen, ti->forbork, fblen);
        if (ti->post != NULL)
            memcpy(encoded_inner + prelen + fblen, ti->post, postlen);
        if (!TEST_true(seal_encoded_inner(msgout, msgoutlen,
                encoded_inner, encoded_innerlen,
                msg, msglen, echoffset, echlen)))
            return 0;
        OPENSSL_free(encoded_inner);
        return 1;
    }

    if (testcase == TESTCASE_ECH && is_ch == 1) {
        if (testiter >= (int)OSSL_NELEM(test_echs))
            return 0;
        to = &test_echs[testiter];
        if (!TEST_true(ossl_ech_helper_get_ch_offsets((const unsigned char *)msg
                    + SSL3_RT_HEADER_LENGTH
                    + SSL3_HM_HEADER_LENGTH,
                msglen
                    - SSL3_RT_HEADER_LENGTH
                    - SSL3_HM_HEADER_LENGTH,
                &sessid, &exts, &extlens,
                &echoffset, &echtype, &echlen,
                &snioffset, &snilen, &inner)))
            return 0;
        /* if it doesn't have an ECH just copy the entire input to output */
        if (echoffset == 0) {
            if (!TEST_ptr(*msgout = OPENSSL_memdup(msg, msglen)))
                return 0;
            *msgoutlen = msglen;
            return 1;
        }
        /* check for editing errors, the +4 is for ext type + len */
        if (to->len > (echlen + 4)) {
            TEST_info("manual sizing error");
            return 0;
        }
        if (!TEST_ptr(*msgout = OPENSSL_memdup(msg, msglen)))
            return 0;
        *msgoutlen = msglen;
        /*
         * overwrite (some of) the outer ECH, in contrast to
         * the above case, here we're overwriting the ECH
         * ext type and length as well, the +9 is for record
         * layer framing as before
         */
        if (to->val != NULL) /* keep fuzz checker happy */
            memcpy(*msgout + echoffset + 9, to->val, to->len);
        return 1;
    }

    if (testcase == TESTCASE_SH && is_sh == 1) {
        if (testiter >= (int)OSSL_NELEM(test_shs))
            return 0;
        ts = &test_shs[testiter];
        if (ts->borkage == 0) {
            if (!TEST_ptr(*msgout = OPENSSL_memdup(msg, msglen)))
                return 0;
            *msgoutlen = msglen;
            return 1;
        }
        /* flip bits in ECH confirmation */
        if ((ts->borkage & OSSL_ECH_BORK_FLIP) != 0) {
            if (!TEST_ptr(*msgout = OPENSSL_memdup(msg, msglen)))
                return 0;
            if ((ts->borkage & OSSL_ECH_BORK_HRR) != 0) {
                rv = ech_get_sh_offsets((unsigned char *)msg + 9,
                    msglen - 9,
                    &exts, &echoffset,
                    &echtype);
                if (!TEST_int_eq(rv, 1))
                    return 0;
                if (echoffset > 0) {
                    (*msgout)[9 + echoffset + 4] = (*msgout)[9 + echoffset + 4] ^ 0xaa;
                }
            } else {
                (*msgout)[9 + 2 + SSL3_RANDOM_SIZE - 4] = (*msgout)[9 + 2 + SSL3_RANDOM_SIZE - 4] ^ 0xaa;
            }
            *msgoutlen = msglen;
            return 1;
        }
        if ((ts->borkage & OSSL_ECH_BORK_REPLACE) != 0 && (ts->borkage & OSSL_ECH_BORK_HRR) != 0) {
            if (!TEST_ptr(*msgout = OPENSSL_memdup(msg, msglen)))
                return 0;
            rv = ech_get_sh_offsets((unsigned char *)msg + 9,
                msglen - 9,
                &exts, &echoffset, &echtype);
            if (!TEST_int_eq(rv, 1))
                return 0;
            if (echoffset > 0)
                memcpy(&((*msgout)[9 + echoffset]), ts->bork, ts->blen);
            *msgoutlen = msglen;
            return 1;
        }
    }

    /* if doing nothing, do that... */
    if (!TEST_ptr(*msgout = OPENSSL_memdup(msg, msglen)))
        return 0;
    *msgoutlen = msglen;
    return 1;
}

static void copy_flags(BIO *bio)
{
    int flags;
    BIO *next = BIO_next(bio);

    flags = BIO_test_flags(next, BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_RWS);
    BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_RWS);
    BIO_set_flags(bio, flags);
}

/*
 * filter to corrupt or copy messages - this is basically copied
 * from the setup in test/sslcorrupttest.c
 */
static int tls_corrupt_write(BIO *bio, const char *in, int inl)
{
    int ret;
    BIO *next = BIO_next(bio);
    char *copy = NULL;
    int copylen = 0;

    ret = corrupt_or_copy(in, inl, &copy, &copylen);
    if (ret == 0)
        goto out;
    ret = BIO_write(next, copy, inl);
    copy_flags(bio);
out:
    OPENSSL_free(copy);
    return ret;
}

/*
 * This and others below are NOOP filters as we only mess
 * with things via the write filter method
 */
static int tls_noop_read(BIO *bio, char *out, int outl)
{
    int ret;
    BIO *next = BIO_next(bio);

    ret = BIO_read(next, out, outl);
    copy_flags(bio);

    return ret;
}

static long tls_noop_ctrl(BIO *bio, int cmd, long num, void *ptr)
{
    long ret;
    BIO *next = BIO_next(bio);

    if (next == NULL)
        return 0;

    switch (cmd) {
    case BIO_CTRL_DUP:
        ret = 0L;
        break;
    default:
        ret = BIO_ctrl(next, cmd, num, ptr);
        break;
    }
    return ret;
}

static int tls_noop_gets(BIO *bio, char *buf, int size)
{
    /* We don't support this - not needed anyway */
    return -1;
}

static int tls_noop_puts(BIO *bio, const char *str)
{
    /* We don't support this - not needed anyway */
    return -1;
}

static int tls_noop_new(BIO *bio)
{
    BIO_set_init(bio, 1);

    return 1;
}

static int tls_noop_free(BIO *bio)
{
    BIO_set_init(bio, 0);

    return 1;
}

#define BIO_TYPE_CUSTOM_CORRUPT (0x80 | BIO_TYPE_FILTER)
#define BIO_TYPE_CUSTOM_SPLIT (0x81 | BIO_TYPE_FILTER)

static BIO_METHOD *method_tls_corrupt = NULL;

/* Note: Not thread safe! */
static const BIO_METHOD *bio_f_tls_corrupt_filter(void)
{
    if (method_tls_corrupt == NULL) {
        method_tls_corrupt = BIO_meth_new(BIO_TYPE_CUSTOM_CORRUPT,
            "TLS corrupt filter");
        if (method_tls_corrupt == NULL
            || !BIO_meth_set_write(method_tls_corrupt, tls_corrupt_write)
            || !BIO_meth_set_read(method_tls_corrupt, tls_noop_read)
            || !BIO_meth_set_puts(method_tls_corrupt, tls_noop_puts)
            || !BIO_meth_set_gets(method_tls_corrupt, tls_noop_gets)
            || !BIO_meth_set_ctrl(method_tls_corrupt, tls_noop_ctrl)
            || !BIO_meth_set_create(method_tls_corrupt, tls_noop_new)
            || !BIO_meth_set_destroy(method_tls_corrupt, tls_noop_free))
            return NULL;
    }
    return method_tls_corrupt;
}

static void bio_f_tls_corrupt_filter_free(void)
{
    BIO_meth_free(method_tls_corrupt);
}

static int test_ch_corrupt(int testidx)
{
    SSL_CTX *sctx = NULL, *cctx = NULL;
    SSL *server = NULL, *client = NULL;
    BIO *c_to_s_fbio;
    int testresult = 0, err = 0, connrv = 0, err_reason = 0;
    int exp_err = SSL_ERROR_NONE;
    TEST_ECHINNER *ti = NULL;
    const char *err_str = NULL;

    testcase = TESTCASE_CH;
    testiter = testidx;
    ti = &test_inners[testidx];
    if (verbose)
        TEST_info("Starting #%d", testidx + 1);
    if (!TEST_true(create_ssl_ctx_pair(NULL, TLS_server_method(),
            TLS_client_method(),
            TLS1_3_VERSION, TLS1_3_VERSION,
            &sctx, &cctx, cert, privkey)))
        return 0;
    /* set server to be willing to only accept TLSv1.2 for test case 23 */
    if (testidx == 22
        && !TEST_true(SSL_CTX_set_max_proto_version(sctx, TLS1_2_VERSION)))
        goto end;
    /* set server to be willing to accept TLSv1.2 for test case 24 */
    if (testidx == 23
        && !TEST_true(SSL_CTX_set_min_proto_version(sctx, TLS1_2_VERSION)))
        goto end;
    /* set client/server to be willing to accept TLSv1.2 for test case 25 */
    if (testidx == 24
        && !TEST_true(SSL_CTX_set_min_proto_version(sctx, TLS1_2_VERSION))
        && !TEST_true(SSL_CTX_set_min_proto_version(cctx, TLS1_2_VERSION)))
        goto end;
    if (!TEST_true(SSL_CTX_set1_echstore(sctx, es)))
        goto end;
    if (!TEST_ptr(c_to_s_fbio = BIO_new(bio_f_tls_corrupt_filter())))
        goto end;
    /* BIO is freed by create_ssl_connection on error */
    if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL,
            c_to_s_fbio)))
        goto end;
    if (!TEST_true(SSL_set1_ech_config_list(client, (unsigned char *)echconfig,
            echconfiglen)))
        goto end;
    if (!TEST_true(SSL_set_tlsext_host_name(client, "foo.example.com")))
        goto end;
    exp_err = SSL_ERROR_SSL;
    if (ti->err_expected == 0)
        exp_err = SSL_ERROR_NONE;
    connrv = create_ssl_connection(server, client, exp_err);
    if (!TEST_int_eq(connrv, ti->rv_expected))
        goto end;
    if (verbose) {
        err_str = ERR_reason_error_string(ti->err_expected);
        err_reason = ERR_GET_REASON(ti->err_expected);
        TEST_info("Expected error: %d/%s", err_reason, err_str);
    }
    if (connrv == 0) {
        do {
            err = ERR_get_error();
            if (err == 0) {
                TEST_error("ECH corruption: Unexpected error");
                goto end;
            }
            err_reason = ERR_GET_REASON(err);
            err_str = ERR_reason_error_string(err);
            if (verbose)
                TEST_info("Error reason: %d/%s", err_reason, err_str);
        } while (err_reason != ti->err_expected);
    }
    testresult = 1;
end:
    SSL_free(server);
    SSL_free(client);
    SSL_CTX_free(sctx);
    SSL_CTX_free(cctx);
    return testresult;
}

static int test_sh_corrupt(int testidx)
{
    SSL_CTX *sctx = NULL, *cctx = NULL;
    SSL *server = NULL, *client = NULL;
    BIO *s_to_c_fbio;
    TEST_SH *ts = NULL;
    int testresult = 0, err = 0, connrv = 0, err_reason = 0;
    int exp_err = SSL_ERROR_NONE;
    unsigned char *retryconfig = NULL;
    size_t retryconfiglen = 0;
    const char *err_str = NULL;

    testcase = TESTCASE_SH;
    testiter = testidx;
    ts = &test_shs[testidx];
    if (verbose)
        TEST_info("Starting #%d", testidx + 1);
    if (!TEST_true(create_ssl_ctx_pair(NULL, TLS_server_method(),
            TLS_client_method(),
            TLS1_3_VERSION, TLS1_3_VERSION,
            &sctx, &cctx, cert, privkey)))
        return 0;
    if (!TEST_true(SSL_CTX_set1_echstore(sctx, es)))
        goto end;
    if (!TEST_ptr(s_to_c_fbio = BIO_new(bio_f_tls_corrupt_filter())))
        goto end;
    /* BIO is freed by create_ssl_connection on error */
    if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client,
            s_to_c_fbio, NULL)))
        goto end;
    if ((ts->borkage & OSSL_ECH_BORK_GREASE) != 0) {
        if (!TEST_true(SSL_set_options(client, SSL_OP_ECH_GREASE)))
            goto end;
    } else {
        if (!TEST_true(SSL_set1_ech_config_list(client,
                (unsigned char *)echconfig,
                echconfiglen)))
            goto end;
    }
    if (!TEST_true(SSL_set_tlsext_host_name(client, "foo.example.com")))
        goto end;
    if ((ts->borkage & OSSL_ECH_BORK_HRR) != 0
        && !TEST_true(SSL_set1_groups_list(server, "P-384")))
        goto end;
    exp_err = SSL_ERROR_SSL;
    if (ts->err_expected == 0)
        exp_err = SSL_ERROR_NONE;
    connrv = create_ssl_connection(server, client, exp_err);
    if (!TEST_int_eq(connrv, ts->rv_expected))
        goto end;
    if (connrv == 1 && (ts->borkage & OSSL_ECH_BORK_GREASE) != 0) {
        if (!TEST_true(SSL_ech_get1_retry_config(client, &retryconfig,
                &retryconfiglen))
            || !TEST_ptr(retryconfig)
            || !TEST_int_ne((int)retryconfiglen, 0))
            goto end;
    }
    if (verbose) {
        err_str = ERR_reason_error_string(ts->err_expected);
        err_reason = ERR_GET_REASON(ts->err_expected);
        TEST_info("Expected error: %d/%s", err_reason, err_str);
    }
    if (connrv == 0) {
        do {
            err = ERR_get_error();
            if (err == 0) {
                TEST_error("ECH corruption: Unexpected error");
                goto end;
            }
            err_reason = ERR_GET_REASON(err);
            err_str = ERR_reason_error_string(err);
            if (verbose)
                TEST_info("Error reason: %d/%s", err_reason, err_str);
        } while (err_reason != ts->err_expected);
    }
    testresult = 1;
end:
    OPENSSL_free(retryconfig);
    SSL_free(server);
    SSL_free(client);
    SSL_CTX_free(sctx);
    SSL_CTX_free(cctx);
    return testresult;
}

typedef enum OPTION_choice {
    OPT_ERR = -1,
    OPT_EOF = 0,
    OPT_VERBOSE,
    OPT_TEST_ENUM
} OPTION_CHOICE;

static int test_ech_corrupt(int testidx)
{
    SSL_CTX *sctx = NULL, *cctx = NULL;
    SSL *server = NULL, *client = NULL;
    BIO *c_to_s_fbio;
    int testresult = 0, err = 0, connrv = 0, err_reason = 0;
    int exp_err = SSL_ERROR_NONE;
    TEST_ECHOUTER *to = NULL;
    const char *err_str = NULL;

    testcase = TESTCASE_ECH;
    testiter = testidx;
    to = &test_echs[testidx];
    if (verbose)
        TEST_info("Starting #%d", testidx + 1);
    if (!TEST_true(create_ssl_ctx_pair(NULL, TLS_server_method(),
            TLS_client_method(),
            TLS1_3_VERSION, TLS1_3_VERSION,
            &sctx, &cctx, cert, privkey)))
        return 0;
    if (!TEST_true(SSL_CTX_set1_echstore(sctx, es)))
        goto end;
    if (!TEST_ptr(c_to_s_fbio = BIO_new(bio_f_tls_corrupt_filter())))
        goto end;
    /* BIO is freed by create_ssl_connection on error */
    if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL,
            c_to_s_fbio)))
        goto end;
    if (!TEST_true(SSL_set1_ech_config_list(client, (unsigned char *)echconfig,
            echconfiglen)))
        goto end;
    if (!TEST_true(SSL_set_tlsext_host_name(client, "foo.example.com")))
        goto end;
    exp_err = SSL_ERROR_SSL;
    if (to->err_expected == 0)
        exp_err = SSL_ERROR_NONE;
    connrv = create_ssl_connection(server, client, exp_err);
    if (!TEST_int_eq(connrv, to->rv_expected))
        goto end;
    if (verbose) {
        err_str = ERR_reason_error_string(to->err_expected);
        err_reason = ERR_GET_REASON(to->err_expected);
        TEST_info("Expected error: %d/%s", err_reason, err_str);
    }
    if (connrv == 0) {
        do {
            err = ERR_get_error();
            if (err == 0) {
                TEST_error("ECH corruption: Unexpected error");
                goto end;
            }
            err_reason = ERR_GET_REASON(err);
            err_str = ERR_reason_error_string(err);
            if (verbose)
                TEST_info("Error reason: %d/%s", err_reason, err_str);
        } while (err_reason != to->err_expected);
    }
    testresult = 1;
end:
    SSL_free(server);
    SSL_free(client);
    SSL_CTX_free(sctx);
    SSL_CTX_free(cctx);
    return testresult;
}

/*
 * Callback that corrupts a server Finished message.
 * This doesn't seem to be documented, but the buffer here is the actual
 * message and not a copy just for the callback, so we can corrupt it by
 * flipping bits in the last octet of the server Finished.  Presumably changing
 * lengths would cause other breakage, but the below currently causes the
 * `memcmp()` to fail in the client's call of `tls_process_finished()` which
 * produces the desired effect of causing the TLS session to fail both because
 * of ECH-required and subsequently because of a later handshake failure
 * resulting in us not making the retry-configs available to the client.
 */
static void corrupt_server_finished(int write_p, int version, int content_type,
    const void *buf, size_t msglen, SSL *ssl, void *arg)
{
    unsigned char *msg = (unsigned char *)buf;

    if (write_p == 0 && content_type == SSL3_RT_HANDSHAKE
        && msg[0] == SSL3_MT_FINISHED)
        msg[msglen - 1] ^= 0xAA;
}

/*
 * Test roundtrip with wrong ECHConfig, with and without corrupting
 * the server Finished to check that retry-configs are not made
 * available to the client in the latter case.
 */
static int ech_retry_config_test(int idx)
{
    int res = 0, clientstatus, serverstatus;
    SSL_CTX *cctx = NULL, *sctx = NULL;
    SSL *clientssl = NULL, *serverssl = NULL;
    char *cinner = NULL, *couter = NULL, *sinner = NULL, *souter = NULL;
    unsigned char *retryconfig = NULL;
    size_t retryconfiglen = 0;
    int err = 0, err_reason = 0, exp_err = ERR_R_OSSL_STORE_LIB;
    const char *err_str = NULL;

    if (!TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(),
            TLS_client_method(),
            TLS1_3_VERSION, TLS1_3_VERSION,
            &sctx, &cctx, cert, privkey)))
        goto end;
    if (!TEST_true(SSL_CTX_set1_echstore(sctx, es)))
        goto end;
    if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl,
            &clientssl, NULL, NULL)))
        goto end;
    if (!TEST_true(SSL_set_tlsext_host_name(clientssl, "foo.example.com")))
        goto end;
    /* set a real but wrong ECHConfig */
    if (!TEST_true(SSL_set1_ech_config_list(clientssl, (unsigned char *)ec_kp2,
            ec_kp2len)))
        goto end;
    if (idx == 1) /* corrupt as desired */
        SSL_set_msg_callback(clientssl, corrupt_server_finished);
    /* real but wrong => failure, due to ECH */
    if (!TEST_false(create_ssl_connection(serverssl, clientssl,
            SSL_R_ECH_REQUIRED)))
        goto end;
    serverstatus = SSL_ech_get1_status(serverssl, &sinner, &souter);
    if (verbose)
        TEST_info("ech_retry_config_test: server status %d, %s, %s",
            serverstatus, sinner, souter);
    if (!TEST_int_eq(serverstatus, SSL_ECH_STATUS_GREASE))
        goto end;
    /* override cert verification */
    SSL_set_verify_result(clientssl, X509_V_OK);
    clientstatus = SSL_ech_get1_status(clientssl, &cinner, &couter);
    if (verbose)
        TEST_info("ech_retry_config_test: client status %d, %s, %s",
            clientstatus, cinner, couter);
    if (!TEST_int_eq(clientstatus, SSL_ECH_STATUS_FAILED_ECH))
        goto end;
    if (idx == 0) { /* no corruption, retry-configs made available */
        if (!TEST_true(SSL_ech_get1_retry_config(clientssl, &retryconfig,
                &retryconfiglen)))
            goto end;
        if (!TEST_ptr(retryconfig))
            goto end;
        if (!TEST_int_ne((int)retryconfiglen, 0))
            goto end;
        if (verbose)
            TEST_info("ech_retry_config_test: retryconfglen: %d\n", (int)retryconfiglen);
        /* we kow the size to expect as the configs are hard-coded above */
        if (!TEST_size_t_eq(retryconfiglen, 64))
            goto end;
    } else { /* corruption, retry-configs NOT made available */
        if (!TEST_false(SSL_ech_get1_retry_config(clientssl, &retryconfig,
                &retryconfiglen)))
            goto end;
        /* check we got the specific error expected */
        err_str = ERR_reason_error_string(exp_err);
        err_reason = ERR_GET_REASON(exp_err);
        TEST_info("ech_retry_config_test Expected error: %d/%s",
            err_reason, err_str);
        do {
            err = ERR_get_error();
            if (err == 0) {
                TEST_error("ech_retry_config_test: Unexpected error");
                goto end;
            }
            err_reason = ERR_GET_REASON(err);
            err_str = ERR_reason_error_string(err);
            if (verbose)
                TEST_info("ech_retry_config_test Actual error: %d/%s",
                    err_reason, err_str);
        } while (err_reason != exp_err);
        if (verbose)
            TEST_info("ech_retry_config_test: retry configs withheld\n");
    }
    res = 1;
end:
    OPENSSL_free(sinner);
    OPENSSL_free(souter);
    OPENSSL_free(cinner);
    OPENSSL_free(couter);
    OPENSSL_free(retryconfig);
    SSL_free(clientssl);
    SSL_free(serverssl);
    SSL_CTX_free(cctx);
    SSL_CTX_free(sctx);
    return res;
}

const OPTIONS *test_get_options(void)
{
    static const OPTIONS test_options[] = {
        OPT_TEST_OPTIONS_DEFAULT_USAGE,
        { "v", OPT_VERBOSE, '-', "Enable verbose mode" },
        { OPT_HELP_STR, 1, '-', "Run ECH Corruption tests\n" },
        { NULL }
    };
    return test_options;
}

int setup_tests(void)
{
    OPTION_CHOICE o;
    BIO *in = NULL;

    while ((o = opt_next()) != OPT_EOF) {
        switch (o) {
        case OPT_VERBOSE:
            verbose = 1;
            break;
        case OPT_TEST_CASES:
            break;
        default:
            return 0;
        }
    }
    if (!test_skip_common_options()) {
        TEST_error("Error parsing test options\n");
        return 0;
    }
    certsdir = test_get_argument(0);
    if (certsdir == NULL)
        certsdir = DEF_CERTS_DIR;
    cert = test_mk_file_path(certsdir, "servercert.pem");
    if (cert == NULL)
        goto err;
    privkey = test_mk_file_path(certsdir, "serverkey.pem");
    if (privkey == NULL)
        goto err;

    /* make an OSSL_ECHSTORE for pem_kp1 */
    if ((in = BIO_new(BIO_s_mem())) == NULL
        || BIO_write(in, pem_kp1, (int)strlen(pem_kp1)) <= 0
        || !TEST_ptr(es = OSSL_ECHSTORE_new(libctx, propq))
        || !TEST_true(OSSL_ECHSTORE_read_pem(es, in, OSSL_ECH_FOR_RETRY)))
        goto err;
    BIO_free_all(in);
    in = NULL;
    hpke_infolen = bin_echconfiglen + 200;
    if (!TEST_ptr(hpke_info = OPENSSL_malloc(hpke_infolen)))
        goto err;
    /* +/- 2 is to drop the ECHConfigList length at the start */
    if (!TEST_true(ossl_ech_make_enc_info((unsigned char *)bin_echconfig + 2,
            bin_echconfiglen - 2,
            hpke_info, &hpke_infolen)))
        goto err;
    ADD_ALL_TESTS(test_ch_corrupt, OSSL_NELEM(test_inners));
    ADD_ALL_TESTS(test_sh_corrupt, OSSL_NELEM(test_shs));
    ADD_ALL_TESTS(test_ech_corrupt, OSSL_NELEM(test_echs));
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    ADD_ALL_TESTS(ech_retry_config_test, 2);
#else
    /*
     * It seems fuzz tests cause our corruption to not work so we'll skip doing
     * that. There's an ifdef'd code fragment in `tls_process_finished()` for
     * when fuzzing that I guess causes that, but it's ok that we only do the
     * corruption test when not fuzzing. We still do the (first) non-corrupt
     * test to avoid a warning that `ech_retry_config_test()` isn't called.
     */
    ADD_ALL_TESTS(ech_retry_config_test, 1);
#endif
    return 1;
err:
    BIO_free_all(in);
    return 0;
}

void cleanup_tests(void)
{
    bio_f_tls_corrupt_filter_free();
    OPENSSL_free(cert);
    OPENSSL_free(privkey);
    OPENSSL_free(hpke_info);
    OSSL_ECHSTORE_free(es);
}
