SOL_API struct sol_message_digest *
sol_message_digest_new(const struct sol_message_digest_config *config)
{
    const struct sol_message_digest_info *dinfo;
    struct sol_message_digest *handle;
    struct sol_message_digest_common_new_params params;
    struct sol_str_slice algorithm;

    errno = EINVAL;
    SOL_NULL_CHECK(config, NULL);
    SOL_NULL_CHECK(config->on_digest_ready, NULL);
    SOL_NULL_CHECK(config->algorithm, NULL);

#ifndef SOL_NO_API_VERSION
    if (config->api_version != SOL_MESSAGE_DIGEST_CONFIG_API_VERSION) {
        SOL_WRN("sol_message_digest_config->api_version=%" PRIu16 ", "
            "expected version is %" PRIu16 ".",
            config->api_version, SOL_MESSAGE_DIGEST_CONFIG_API_VERSION);
        return NULL;
    }
#endif

    algorithm = sol_str_slice_from_str(config->algorithm);
    if (!sol_str_table_ptr_lookup(_available_digests, algorithm, &dinfo)) {
        SOL_WRN("failed to get digest algorithm \"%s\".",
            config->algorithm);
        return NULL;
    }

    params.config = config;
    params.ops = &dinfo->ops;
    params.context_size = dinfo->context_size;
    params.digest_size = dinfo->digest_size;
    params.context_template = NULL;

    handle = sol_message_digest_common_new(params);
    SOL_NULL_CHECK(handle, NULL);

    dinfo->init(handle);

    return handle;
}
SOL_API struct sol_message_digest *
sol_message_digest_new(const struct sol_message_digest_config *config)
{
    int (*init_fn)(struct sol_message_digest *, const EVP_MD *, const struct sol_str_slice);
    struct sol_message_digest_common_new_params params;
    const EVP_MD *md;
    struct sol_message_digest *handle;
    int errno_bkp;

    errno = EINVAL;
    SOL_NULL_CHECK(config, NULL);
    SOL_NULL_CHECK(config->on_digest_ready, NULL);
    SOL_NULL_CHECK(config->algorithm, NULL);

#ifndef SOL_NO_API_VERSION
    if (config->api_version != SOL_MESSAGE_DIGEST_CONFIG_API_VERSION) {
        SOL_WRN("sol_message_digest_config->api_version=%" PRIu16 ", "
            "expected version is %" PRIu16 ".",
            config->api_version, SOL_MESSAGE_DIGEST_CONFIG_API_VERSION);
        return NULL;
    }
#endif

    if (!did_openssl_load_digests) {
        OpenSSL_add_all_digests();
        did_openssl_load_digests = true;
    }

    params.config = config;
    params.ops = NULL;

    md = EVP_get_digestbyname(config->algorithm);
    if (md) {
        params.context_handle = EVP_MD_CTX_new();
        params.context_free = (void (*)(void *))EVP_MD_CTX_free;
        init_fn = _sol_message_digest_evp_init;
        params.ops = &_sol_message_digest_evp_ops;
        SOL_DBG("using evp, md=%p, algorithm=\"%s\"", md, config->algorithm);
    } else if (streqn(config->algorithm, "hmac(", strlen("hmac("))) {
        const char *p = config->algorithm + strlen("hmac(");
        size_t len = strlen(p);
        params.context_handle = HMAC_CTX_new();
        params.context_free = (void (*)(void *))HMAC_CTX_free;
        if (len > 1 && p[len - 1] == ')') {
            char *mdname = strndupa(p, len - 1);
            md = EVP_get_digestbyname(mdname);
            if (!md) {
                SOL_WRN("failed to get digest algorithm \"%s\" for \"%s\".",
                    mdname, config->algorithm);
                return NULL;
            }
            init_fn = _sol_message_digest_hmac_init;
            params.ops = &_sol_message_digest_hmac_ops;
            SOL_DBG("using hmac, md=%p, algorithm=\"%s\"", md, mdname);
        }
    }

    if (!params.ops) {
        SOL_WRN("failed to get digest algorithm \"%s\".", config->algorithm);
        return NULL;
    }

    params.digest_size = EVP_MD_size(md);

    handle = sol_message_digest_common_new(params);
    SOL_NULL_CHECK(handle, NULL);

    errno = init_fn(handle, md, config->key);
    if (errno)
        goto error;

    return handle;

error:
    errno_bkp = errno;
    sol_message_digest_del(handle);
    errno = errno_bkp;
    return NULL;
}