static void *evp_keyexch_from_dispatch(int name_id,
                                       const OSSL_DISPATCH *fns,
                                       OSSL_PROVIDER *prov,
                                       void *vkeymgmt_data)
{
    /*
     * Key exchange cannot work without a key, and key management
     * from the same provider to manage its keys.  We therefore fetch
     * a key management method using the same algorithm and properties
     * and pass that down to evp_generic_fetch to be passed on to our
     * evp_keyexch_from_dispatch, which will attach the key management
     * method to the newly created key exchange method as long as the
     * provider matches.
     */
    struct keymgmt_data_st *keymgmt_data = vkeymgmt_data;
    EVP_KEYMGMT *keymgmt =
        evp_keymgmt_fetch_by_number(keymgmt_data->ctx, name_id,
                                    keymgmt_data->properties);
    EVP_KEYEXCH *exchange = NULL;
    int fncnt = 0, paramfncnt = 0;

    if (keymgmt == NULL || EVP_KEYMGMT_provider(keymgmt) != prov) {
        ERR_raise(ERR_LIB_EVP, EVP_R_NO_KEYMGMT_AVAILABLE);
        goto err;
    }

    if ((exchange = evp_keyexch_new(prov)) == NULL) {
        ERR_raise(ERR_LIB_EVP, ERR_R_MALLOC_FAILURE);
        goto err;
    }

    exchange->name_id = name_id;
    exchange->keymgmt = keymgmt;
    keymgmt = NULL;              /* avoid double free on failure below */

    for (; fns->function_id != 0; fns++) {
        switch (fns->function_id) {
        case OSSL_FUNC_KEYEXCH_NEWCTX:
            if (exchange->newctx != NULL)
                break;
            exchange->newctx = OSSL_get_OP_keyexch_newctx(fns);
            fncnt++;
            break;
        case OSSL_FUNC_KEYEXCH_INIT:
            if (exchange->init != NULL)
                break;
            exchange->init = OSSL_get_OP_keyexch_init(fns);
            fncnt++;
            break;
        case OSSL_FUNC_KEYEXCH_SET_PEER:
            if (exchange->set_peer != NULL)
                break;
            exchange->set_peer = OSSL_get_OP_keyexch_set_peer(fns);
            break;
        case OSSL_FUNC_KEYEXCH_DERIVE:
            if (exchange->derive != NULL)
                break;
            exchange->derive = OSSL_get_OP_keyexch_derive(fns);
            fncnt++;
            break;
        case OSSL_FUNC_KEYEXCH_FREECTX:
            if (exchange->freectx != NULL)
                break;
            exchange->freectx = OSSL_get_OP_keyexch_freectx(fns);
            fncnt++;
            break;
        case OSSL_FUNC_KEYEXCH_DUPCTX:
            if (exchange->dupctx != NULL)
                break;
            exchange->dupctx = OSSL_get_OP_keyexch_dupctx(fns);
            break;
        case OSSL_FUNC_KEYEXCH_SET_CTX_PARAMS:
            if (exchange->set_ctx_params != NULL)
                break;
            exchange->set_ctx_params = OSSL_get_OP_keyexch_set_ctx_params(fns);
            paramfncnt++;
            break;
        case OSSL_FUNC_KEYEXCH_SETTABLE_CTX_PARAMS:
            if (exchange->settable_ctx_params != NULL)
                break;
            exchange->settable_ctx_params
                = OSSL_get_OP_keyexch_settable_ctx_params(fns);
            paramfncnt++;
            break;
        }
    }
    if (fncnt != 4 || (paramfncnt != 0 && paramfncnt != 2)) {
        /*
         * In order to be a consistent set of functions we must have at least
         * a complete set of "exchange" functions: init, derive, newctx,
         * and freectx. The set_ctx_params and settable_ctx_params functions are
         * optional, but if one of them is present then the other one must also
         * be present. The dupctx and set_peer functions are optional.
         */
        EVPerr(EVP_F_EVP_KEYEXCH_FROM_DISPATCH,
               EVP_R_INVALID_PROVIDER_FUNCTIONS);
        goto err;
    }

    return exchange;

 err:
    EVP_KEYEXCH_free(exchange);
    EVP_KEYMGMT_free(keymgmt);
    return NULL;
}