const SSL_CIPHER *ssl3_choose_cipher(SSL *s, STACK_OF(SSL_CIPHER) *clnt,
                                     STACK_OF(SSL_CIPHER) *srvr)
{
    const SSL_CIPHER *c, *ret = NULL;
    STACK_OF(SSL_CIPHER) *prio, *allow;
    int i, ii, ok;
    unsigned long alg_k = 0, alg_a = 0, mask_k = 0, mask_a = 0;
#ifndef OPENSSL_NO_CHACHA
    STACK_OF(SSL_CIPHER) *prio_chacha = NULL;
#endif

    /* Let's see which ciphers we can support */

    /*
     * Do not set the compare functions, because this may lead to a
     * reordering by "id". We want to keep the original ordering. We may pay
     * a price in performance during sk_SSL_CIPHER_find(), but would have to
     * pay with the price of sk_SSL_CIPHER_dup().
     */

#ifdef CIPHER_DEBUG
    fprintf(stderr, "Server has %d from %p:\n", sk_SSL_CIPHER_num(srvr),
            (void *)srvr);
    for (i = 0; i < sk_SSL_CIPHER_num(srvr); ++i) {
        c = sk_SSL_CIPHER_value(srvr, i);
        fprintf(stderr, "%p:%s\n", (void *)c, c->name);
    }
    fprintf(stderr, "Client sent %d from %p:\n", sk_SSL_CIPHER_num(clnt),
            (void *)clnt);
    for (i = 0; i < sk_SSL_CIPHER_num(clnt); ++i) {
        c = sk_SSL_CIPHER_value(clnt, i);
        fprintf(stderr, "%p:%s\n", (void *)c, c->name);
    }
#endif

    /* SUITE-B takes precedence over server preference and ChaCha priortiy */
    if (tls1_suiteb(s)) {
        prio = srvr;
        allow = clnt;
    } else if (s->options & SSL_OP_CIPHER_SERVER_PREFERENCE) {
        prio = srvr;
        allow = clnt;
#ifndef OPENSSL_NO_CHACHA
        /* If ChaCha20 is at the top of the client preference list,
           and there are ChaCha20 ciphers in the server list, then
           temporarily prioritize all ChaCha20 ciphers in the servers list. */
        if (s->options & SSL_OP_PRIORITIZE_CHACHA && sk_SSL_CIPHER_num(clnt) > 0) {
            c = sk_SSL_CIPHER_value(clnt, 0);
            if (c->algorithm_enc == SSL_CHACHA20POLY1305) {
                /* ChaCha20 is client preferred, check server... */
                int num = sk_SSL_CIPHER_num(srvr);
                int found = 0;
                for (i = 0; i < num; i++) {
                    c = sk_SSL_CIPHER_value(srvr, i);
                    if (c->algorithm_enc == SSL_CHACHA20POLY1305) {
                        found = 1;
                        break;
                    }
                }
                if (found) {
                    prio_chacha = sk_SSL_CIPHER_new_null();
                    /* if reserve fails, then there's likely a memory issue */
                    if (prio_chacha != NULL) {
                        /* Put all ChaCha20 at the top, starting with the one we just found */
                        sk_SSL_CIPHER_push(prio_chacha, c);
                        for (i++; i < num; i++) {
                            c = sk_SSL_CIPHER_value(srvr, i);
                            if (c->algorithm_enc == SSL_CHACHA20POLY1305)
                                sk_SSL_CIPHER_push(prio_chacha, c);
                        }
                        /* Pull in the rest */
                        for (i = 0; i < num; i++) {
                            c = sk_SSL_CIPHER_value(srvr, i);
                            if (c->algorithm_enc != SSL_CHACHA20POLY1305)
                                sk_SSL_CIPHER_push(prio_chacha, c);
                        }
                        prio = prio_chacha;
                    }
                }
            }
        }
# endif
    } else {
        prio = clnt;
        allow = srvr;
    }

    if (!SSL_IS_TLS13(s)) {
        tls1_set_cert_validity(s);
        ssl_set_masks(s);
    }

    for (i = 0; i < sk_SSL_CIPHER_num(prio); i++) {
        c = sk_SSL_CIPHER_value(prio, i);

        /* Skip ciphers not supported by the protocol version */
        if (!SSL_IS_DTLS(s) &&
            ((s->version < c->min_tls) || (s->version > c->max_tls)))
            continue;
        if (SSL_IS_DTLS(s) &&
            (DTLS_VERSION_LT(s->version, c->min_dtls) ||
             DTLS_VERSION_GT(s->version, c->max_dtls)))
            continue;

        /*
         * Since TLS 1.3 ciphersuites can be used with any auth or
         * key exchange scheme skip tests.
         */
        if (!SSL_IS_TLS13(s)) {
            mask_k = s->s3->tmp.mask_k;
            mask_a = s->s3->tmp.mask_a;
#ifndef OPENSSL_NO_SRP
            if (s->srp_ctx.srp_Mask & SSL_kSRP) {
                mask_k |= SSL_kSRP;
                mask_a |= SSL_aSRP;
            }
#endif

            alg_k = c->algorithm_mkey;
            alg_a = c->algorithm_auth;

#ifndef OPENSSL_NO_PSK
            /* with PSK there must be server callback set */
            if ((alg_k & SSL_PSK) && s->psk_server_callback == NULL)
                continue;
#endif                          /* OPENSSL_NO_PSK */

            ok = (alg_k & mask_k) && (alg_a & mask_a);
#ifdef CIPHER_DEBUG
            fprintf(stderr, "%d:[%08lX:%08lX:%08lX:%08lX]%p:%s\n", ok, alg_k,
                    alg_a, mask_k, mask_a, (void *)c, c->name);
#endif

#ifndef OPENSSL_NO_EC
            /*
             * if we are considering an ECC cipher suite that uses an ephemeral
             * EC key check it
             */
            if (alg_k & SSL_kECDHE)
                ok = ok && tls1_check_ec_tmp_key(s, c->id);
#endif                          /* OPENSSL_NO_EC */

            if (!ok)
                continue;
        }
        ii = sk_SSL_CIPHER_find(allow, c);
        if (ii >= 0) {
            /* Check security callback permits this cipher */
            if (!ssl_security(s, SSL_SECOP_CIPHER_SHARED,
                              c->strength_bits, 0, (void *)c))
                continue;
#if !defined(OPENSSL_NO_EC)
            if ((alg_k & SSL_kECDHE) && (alg_a & SSL_aECDSA)
                && s->s3->is_probably_safari) {
                if (!ret)
                    ret = sk_SSL_CIPHER_value(allow, ii);
                continue;
            }
#endif
            ret = sk_SSL_CIPHER_value(allow, ii);
            break;
        }
    }
#ifndef OPENSSL_NO_CHACHA
    sk_SSL_CIPHER_free(prio_chacha);
#endif
    return ret;
}