/*
 * Copyright 2014 Tushar Gohad, Kevin M Greenan, Eric Lambert, Mark Storer
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice, this
 * list of conditions and the following disclaimer in the documentation and/or
 * other materials provided with the distribution.  THIS SOFTWARE IS PROVIDED BY
 * THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * liberasurecode API implementation
 *
 * vi: set noai tw=79 ts=4 sw=4:
 */

#include <assert.h>
#include <zlib.h>
#include "list.h"
#include "erasurecode.h"
#include "erasurecode_backend.h"
#include "erasurecode_helpers.h"
#include "erasurecode_helpers_ext.h"
#include "erasurecode_preprocessing.h"
#include "erasurecode_postprocessing.h"
#include "erasurecode_stdinc.h"

#include "alg_sig.h"
#include "erasurecode_log.h"

/* =~=*=~==~=*=~==~=*=~= Supported EC backends =~=*=~==~=*=~==~=*=~==~=*=~== */

/* EC backend references */
extern struct ec_backend_common backend_null;
extern struct ec_backend_common backend_flat_xor_hd;
extern struct ec_backend_common backend_jerasure_rs_vand;
extern struct ec_backend_common backend_jerasure_rs_cauchy;
extern struct ec_backend_common backend_isa_l_rs_vand;
extern struct ec_backend_common backend_shss;
extern struct ec_backend_common backend_liberasurecode_rs_vand;
extern struct ec_backend_common backend_isa_l_rs_cauchy;
extern struct ec_backend_common backend_libphazr;

ec_backend_t ec_backends_supported[] = {
    (ec_backend_t) &backend_null,
    (ec_backend_t) &backend_jerasure_rs_vand,
    (ec_backend_t) &backend_jerasure_rs_cauchy,
    (ec_backend_t) &backend_flat_xor_hd,
    (ec_backend_t) &backend_isa_l_rs_vand,
    (ec_backend_t) &backend_shss,
    (ec_backend_t) &backend_liberasurecode_rs_vand,
    (ec_backend_t) &backend_isa_l_rs_cauchy,
    (ec_backend_t) &backend_libphazr,
    NULL,
};

/* backend list to return to the caller */
int num_supported_backends = 0;
char *ec_backends_supported_str[EC_BACKENDS_MAX];

/* =~=*=~==~=*=~==~=*=~= EC backend instance management =~=*=~==~=*=~==~=*= */

/* Registered erasure code backend instances */
SLIST_HEAD(backend_list, ec_backend) active_instances =
    SLIST_HEAD_INITIALIZER(active_instances);
rwlock_t active_instances_rwlock = RWLOCK_INITIALIZER;

/* Backend instance id */
int next_backend_desc = 0;

/**
 * Look up a backend instance by descriptor
 *
 * @returns pointer to a registered liberasurecode instance
 * The caller must hold active_instances_rwlock
 */
ec_backend_t liberasurecode_backend_instance_get_by_desc(int desc)
{
    struct ec_backend *b = NULL;
    SLIST_FOREACH(b, &active_instances, link) {
        if (b->idesc == desc)
            break;
    }
    return b;
}

/**
 * Allocated backend instance descriptor
 *
 * Returns a unique descriptor for a new backend.
 * The caller must hold active_instances_rwlock
 */
int liberasurecode_backend_alloc_desc(void)
{
    for (;;) {
        if (++next_backend_desc <= 0)
            next_backend_desc = 1;
        if (!liberasurecode_backend_instance_get_by_desc(next_backend_desc))
            return next_backend_desc;
    }
}

/**
 * Register a backend instance with liberasurecode
 *
 * @param instance - backend enum
 *
 * @returns new backend descriptor
 */
int liberasurecode_backend_instance_register(ec_backend_t instance)
{
    int desc = -1;  /* descriptor to return */
    int rc = 0;     /* return call value */

    rc = rwlock_wrlock(&active_instances_rwlock);
    if (rc == 0) {
        SLIST_INSERT_HEAD(&active_instances, instance, link);
        desc = liberasurecode_backend_alloc_desc();
        if (desc <= 0)
            goto register_out;
        instance->idesc = desc;
    } else {
        goto exit;
    }

register_out:
    rwlock_unlock(&active_instances_rwlock);
exit:
    return desc;
}

/**
 * Unregister a backend instance
 *
 * @returns 0 on success, non-0 on error
 */
int liberasurecode_backend_instance_unregister(ec_backend_t instance)
{
    int rc = 0;  /* return call value */

    rc = rwlock_wrlock(&active_instances_rwlock);
    if (rc == 0) {
        SLIST_REMOVE(&active_instances, instance, ec_backend, link);
    }  else {
        goto exit;
    }
    rwlock_unlock(&active_instances_rwlock);

exit:
    return rc;
}

/* =~=*=~==~=*=~== liberasurecode backend API helpers =~=*=~==~=*=~== */

static void print_dlerror(const char *caller)
{
    char *msg = dlerror();
    if (NULL == msg)
        log_error("%s: unknown dynamic linking error\n", caller);
    else
        log_error("%s: dynamic linking error %s\n", caller, msg);
}

/* Generic dlopen/dlclose routines */
void* liberasurecode_backend_open(ec_backend_t instance)
{
    if (NULL == instance)
        return NULL;
    /* Use RTLD_LOCAL to avoid symbol collisions */
    return dlopen(instance->common.soname, RTLD_LAZY | RTLD_LOCAL);
}

int liberasurecode_backend_close(ec_backend_t instance)
{
    if (NULL == instance || NULL == instance->desc.backend_sohandle)
        return 0;

    dlclose(instance->desc.backend_sohandle);
    dlerror();    /* Clear any existing errors */

    instance->desc.backend_sohandle = NULL;
    return 0;
}

/* =*=~==~=*=~==~=*=~= liberasurecode init/exit routines =~=*=~==~=*=~==~=*= */

void __attribute__ ((constructor))
liberasurecode_init(void) {
    /* init logging */
    openlog("liberasurecode", LOG_PID | LOG_CONS, LOG_USER);

    /* populate supported backends list as a string */
    {
        int i;
        for (i = 0; ec_backends_supported[i]; ++i) {
            ec_backends_supported_str[i] = strdup(
                    ec_backends_supported[i]->common.name);
        }
        num_supported_backends = i;
    }
}

void __attribute__ ((destructor))
liberasurecode_exit(void) {
    int i;
    for (i = 0; i < num_supported_backends; ++i)
        free(ec_backends_supported_str[i]);
    closelog();
}

/* =~=*=~==~=*=~= liberasurecode frontend API implementation =~=*=~==~=*=~== */

/**
 * Checks if a given backend is available.
 *
 * @param backend_id - one of the supported backends.
 *
 * @returns 1 if a backend is available; 0 otherwise
 */
int liberasurecode_backend_available(const ec_backend_id_t backend_id) {
    struct ec_backend backend;
    if (backend_id >= EC_BACKENDS_MAX)
        return 0;

    backend.desc.backend_sohandle = liberasurecode_backend_open(
            ec_backends_supported[backend_id]);
    if (!backend.desc.backend_sohandle) {
        return 0;
    }

    liberasurecode_backend_close(&backend);
    return 1;
}

/**
 * Create a liberasurecode instance and return a descriptor
 * for use with EC operations (encode, decode, reconstruct)
 *
 * @param id - one of the supported backends as
 *        defined by ec_backend_id_t
 * @param args - arguments to the EC backend
 *        arguments common to all backends
 *          k - number of data fragments
 *          m - number of parity fragments
 *          w - word size, in bits
 *          hd - hamming distance (=m for Reed-Solomon)
 *          ct - fragment checksum type (stored with the fragment metadata)
 *        backend-specific arguments
 *          null_args - arguments for the null backend
 *          flat_xor_hd, jerasure do not require any special args
 *
 * @returns liberasurecode instance descriptor (int > 0)
 */
int liberasurecode_instance_create(const ec_backend_id_t id,
                                   struct ec_args *args)
{
    ec_backend_t instance = NULL;
    struct ec_backend_args bargs;
    if (!args)
        return -EINVALIDPARAMS;

    if (id >= EC_BACKENDS_MAX)
        return -EBACKENDNOTSUPP;

    if (args->k < 0 || args->m < 0)
        return -EINVALIDPARAMS;
    if ((args->k + args->m) > EC_MAX_FRAGMENTS) {
        log_error("Total number of fragments (k + m) must be less than %d\n",
                  EC_MAX_FRAGMENTS);
        return -EINVALIDPARAMS;
    }

    /* Allocate memory for ec_backend instance */
    instance = calloc(1, sizeof(*instance));
    if (NULL == instance)
        return -ENOMEM;

    /* Copy common backend, args struct */
    instance->common = ec_backends_supported[id]->common;
    memcpy(&(bargs.uargs), args, sizeof (struct ec_args));
    instance->args = bargs;

    /* Open backend .so if not already open */
    /* .so handle is returned in instance->desc.backend_sohandle */
    if (!instance->desc.backend_sohandle) {
        instance->desc.backend_sohandle = liberasurecode_backend_open(instance);
        if (!instance->desc.backend_sohandle) {
            /* ignore during init, return the same handle */
            print_dlerror(__func__);
            free(instance);
            return -EBACKENDNOTAVAIL;
        }
    }

    /* Call private init() for the backend */
    instance->desc.backend_desc = instance->common.ops->init(
            &instance->args, instance->desc.backend_sohandle);
    if (NULL == instance->desc.backend_desc) {
        free (instance);
        return -EBACKENDINITERR;
    }

    /* Register instance and return a descriptor/instance id */
    instance->idesc = liberasurecode_backend_instance_register(instance);

    return instance->idesc;
}

/**
 * Close a liberasurecode instance
 *
 * @param desc - liberasurecode descriptor to close
 */
int liberasurecode_instance_destroy(int desc)
{
    ec_backend_t instance = NULL;  /* instance to destroy */
    int rc = 0;                    /* return code */

    instance = liberasurecode_backend_instance_get_by_desc(desc);
    if (NULL == instance)
        return -EBACKENDNOTAVAIL;

    /* Call private exit() for the backend */
    instance->common.ops->exit(instance->desc.backend_desc);

    /* dlclose() backend library */
    liberasurecode_backend_close(instance);

    /* Remove instance from registry */
    rc = liberasurecode_backend_instance_unregister(instance);
    if (rc == 0) {
        free(instance);
    }

    return rc;
}

/**
 * Cleanup structures allocated by librasurecode_encode
 *
 * The caller has no context, so cannot safely free memory
 * allocated by liberasurecode, so it must pass the
 * deallocation responsibility back to liberasurecode.
 *
 * @param desc - liberasurecode descriptor/handle
 *        from liberasurecode_instance_create()
 * @param encoded_data - (char **) array of k data
 *        fragments (char *), allocated by liberasurecode_encode
 * @param encoded_parity - (char **) array of m parity
 *        fragments (char *), allocated by liberasurecode_encode
 * @return 0 in success; -error otherwise
 */
int liberasurecode_encode_cleanup(int desc,
                                  char **encoded_data,
                                  char **encoded_parity)
{
    int i, k, m;

    ec_backend_t instance = liberasurecode_backend_instance_get_by_desc(desc);
    if (NULL == instance) {
        return -EBACKENDNOTAVAIL;
    }

    k = instance->args.uargs.k;
    m = instance->args.uargs.m;

    if (encoded_data) {
        for (i = 0; i < k; i++) {
            free(encoded_data[i]);
        }

        free(encoded_data);
    }

    if (encoded_parity) {
        for (i = 0; i < m; i++) {
            free(encoded_parity[i]);
        }
        free(encoded_parity);
    }

    return 0;
}

/**
 * Erasure encode a data buffer
 *
 * @param desc - liberasurecode descriptor/handle
 *        from liberasurecode_instance_create()
 * @param orig_data - data to encode
 * @param orig_data_size - length of data to encode
 * @param encoded_data - pointer to _output_ array (char **) of k data
 *        fragments (char *), allocated by the callee
 * @param encoded_parity - pointer to _output_ array (char **) of m parity
 *        fragments (char *), allocated by the callee
 * @param fragment_len - pointer to _output_ length of each fragment, assuming
 *        all fragments are the same length
 *
 * @return 0 on success, -error code otherwise
 */
int liberasurecode_encode(int desc,
        const char *orig_data, uint64_t orig_data_size, /* input */
        char ***encoded_data, char ***encoded_parity,   /* output */
        uint64_t *fragment_len)                         /* output */
{
    int k, m;
    int ret = 0;            /* return code */

    int blocksize = 0;      /* length of each of k data elements */

    if (orig_data == NULL) {
        log_error("Pointer to data buffer is null!");
        ret = -EINVALIDPARAMS;
        goto out;
    }

    if (encoded_data == NULL) {
        log_error("Pointer to encoded data buffers is null!");
        return -EINVALIDPARAMS;
    }

    if (encoded_parity == NULL) {
        log_error("Pointer to encoded parity buffers is null!");
        return -EINVALIDPARAMS;
    }

    if (fragment_len == NULL) {
        log_error("Pointer to fragment length is null!");
        ret = -EINVALIDPARAMS;
        goto out;
    }

    ec_backend_t instance = liberasurecode_backend_instance_get_by_desc(desc);
    if (NULL == instance) {
        ret = -EBACKENDNOTAVAIL;
        goto out;
    }

    k = instance->args.uargs.k;
    m = instance->args.uargs.m;

    /*
     * Allocate arrays for data, parity and missing_idxs
     */
    *encoded_data = (char **) alloc_zeroed_buffer(sizeof(char *) * k);
    if (NULL == *encoded_data) {
        log_error("Could not allocate data buffer!");
        goto out;
    }

    *encoded_parity = (char **) alloc_zeroed_buffer(sizeof(char *) * m);
    if (NULL == *encoded_parity) {
        log_error("Could not allocate parity buffer!");
        goto out;
    }

    ret = prepare_fragments_for_encode(instance, k, m, orig_data, orig_data_size,
                                       *encoded_data, *encoded_parity, &blocksize);
    if (ret < 0) {
        // ensure encoded_data/parity point the head of fragment_ptr
        get_fragment_ptr_array_from_data(*encoded_data, *encoded_data, k);
        get_fragment_ptr_array_from_data(*encoded_parity, *encoded_parity, m);
        goto out;
    }

    /* call the backend encode function passing it desc instance */
    ret = instance->common.ops->encode(instance->desc.backend_desc,
                                       *encoded_data, *encoded_parity, blocksize);
    if (ret < 0) {
        // ensure encoded_data/parity point the head of fragment_ptr
        get_fragment_ptr_array_from_data(*encoded_data, *encoded_data, k);
        get_fragment_ptr_array_from_data(*encoded_parity, *encoded_parity, m);
        goto out;
    }

    ret = finalize_fragments_after_encode(instance, k, m, blocksize, orig_data_size,
                                          *encoded_data, *encoded_parity);

    *fragment_len = get_fragment_size((*encoded_data)[0]);

out:
    if (ret) {
        /* Cleanup the allocations we have done */
        liberasurecode_encode_cleanup(desc, *encoded_data, *encoded_parity);
        log_error("Error in liberasurecode_encode %d", ret);
    }
    return ret;
}

/**
 * Cleanup structures allocated by librasurecode_decode
 *
 * The caller has no context, so cannot safely free memory
 * allocated by liberasurecode, so it must pass the
 * deallocation responsibility back to liberasurecode.
 *
 * @param desc - liberasurecode descriptor/handle
 *        from liberasurecode_instance_create()
 * @param data - (char *) buffer of data decoded by
 *        librasurecode_decode
 * @return 0 in success; -error otherwise
 */
int liberasurecode_decode_cleanup(int desc, char *data)
{
    ec_backend_t instance = liberasurecode_backend_instance_get_by_desc(desc);
    if (NULL == instance) {
        return -EBACKENDNOTAVAIL;
    }

    free(data);

    return 0;
}

/**
 * Reconstruct original data from a set of k encoded fragments
 *
 * @param desc - liberasurecode descriptor/handle
 *        from liberasurecode_instance_create()
 * @param available_fragments - erasure encoded fragments (> = k)
 * @param num_fragments - number of fragments being passed in
 * @param fragment_len - length of each fragment (assume they are the same)
 * @param force_metadata_checks - force fragment metadata checks (default: 0)
 * @param out_data - _output_ pointer to decoded data
 * @param out_data_len - _output_ length of decoded output
 * @return 0 on success, -error code otherwise
 */
int liberasurecode_decode(int desc,
        char **available_fragments,                     /* input */
        int num_fragments, uint64_t fragment_len,       /* input */
        int force_metadata_checks,                      /* input */
        char **out_data, uint64_t *out_data_len)        /* output */
{
    int i, j;
    int ret = 0;

    int k = -1, m = -1;
    int orig_data_size = 0;

    int blocksize = 0;
    char **data = NULL;
    char **parity = NULL;
    char **data_segments = NULL;
    char **parity_segments = NULL;
    int *missing_idxs = NULL;

    uint64_t realloc_bm = 0;

    ec_backend_t instance = liberasurecode_backend_instance_get_by_desc(desc);
    if (NULL == instance) {
        ret = -EBACKENDNOTAVAIL;
        goto out;
    }

    if (NULL == available_fragments) {
        log_error("Pointer to encoded fragments buffer is null!");
        ret = -EINVALIDPARAMS;
        goto out;
    }

    if (NULL == out_data) {
        log_error("Pointer to decoded data buffer is null!");
        ret = -EINVALIDPARAMS;
        goto out;
    }

    if (NULL == out_data_len) {
        log_error("Pointer to decoded data length variable is null!");
        ret = -EINVALIDPARAMS;
        goto out;
    }

    k = instance->args.uargs.k;
    m = instance->args.uargs.m;

    if (num_fragments < k) {
        log_error("Not enough fragments to decode, got %d, need %d!",
                  num_fragments, k);
        ret = -EINSUFFFRAGS;
        goto out;
    }

    if (fragment_len < sizeof(fragment_header_t)) {
        log_error("Fragments not long enough to include headers! "
                  "Need %zu, but got %lu.", sizeof(fragment_header_t),
                  (unsigned long)fragment_len);
        ret = -EBADHEADER;
        goto out;
    }
    for (i = 0; i < num_fragments; ++i) {
        /* Verify metadata checksum */
        if (is_invalid_fragment_header(
                (fragment_header_t *) available_fragments[i])) {
            log_error("Invalid fragment header information!");
            ret = -EBADHEADER;
            goto out;
        }
    }

    if (instance->common.id != EC_BACKEND_SHSS && instance->common.id != EC_BACKEND_LIBPHAZR) {
        /* shss (ntt_backend) & libphazr backend must force to decode */
        // TODO: Add a frag and function to handle whether the backend want to decode or not.
        /*
         * Try to re-assebmle the original data before attempting a decode
         */
        ret = fragments_to_string(k, m,
                                  available_fragments, num_fragments,
                                  out_data, out_data_len);

        if (ret == 0) {
            /* We were able to get the original data without decoding! */
            goto out;
        }
    }

    /*
     * Allocate arrays for data, parity and missing_idxs
     */
    data = alloc_zeroed_buffer(sizeof(char*) * k);
    if (NULL == data) {
        log_error("Could not allocate data buffer!");
        goto out;
    }

    parity = alloc_zeroed_buffer(sizeof(char*) * m);
    if (NULL == parity) {
        log_error("Could not allocate parity buffer!");
        goto out;
    }

    missing_idxs = alloc_and_set_buffer(sizeof(char*) * (k + m), -1);
    if (NULL == missing_idxs) {
        log_error("Could not allocate missing_idxs buffer!");
        goto out;
    }

    /* If metadata checks requested, check fragment integrity upfront */
    if (force_metadata_checks) {
        int num_invalid_fragments = 0;
        for (i = 0; i < num_fragments; ++i) {
            if (is_invalid_fragment(desc, available_fragments[i])) {
                ++num_invalid_fragments;
            }
        }
        if ((num_fragments - num_invalid_fragments) < k) {
            ret = -EINSUFFFRAGS;
            log_error("Not enough valid fragments available for decode!");
            goto out;
        }
    }

    /*
     * Separate the fragments into data and parity.  Also determine which
     * pieces are missing.
     */
    ret = get_fragment_partition(k, m, available_fragments, num_fragments,
            data, parity, missing_idxs);

    if (ret < 0) {
        log_error("Could not properly partition the fragments!");
        goto out;
    }

    /*
     * Preparing the fragments for decode.  This will alloc aligned buffers
     * when unaligned buffers were passed in available_fragments.  It passes
     * back a bitmap telling us which buffers need to be freed by us
     * (realloc_bm).
     *
     */
    ret = prepare_fragments_for_decode(k, m,
                                       data, parity, missing_idxs, 
                                       &orig_data_size, &blocksize,
                                       fragment_len, &realloc_bm);
    if (ret < 0) {
        log_error("Could not prepare fragments for decode!");
        goto out;
    }

    data_segments = alloc_zeroed_buffer(k * sizeof(char *));
    parity_segments = alloc_zeroed_buffer(m * sizeof(char *));
    get_data_ptr_array_from_fragments(data_segments, data, k);
    get_data_ptr_array_from_fragments(parity_segments, parity, m);

    /* call the backend decode function passing it desc instance */
    ret = instance->common.ops->decode(instance->desc.backend_desc,
                                       data_segments, parity_segments,
                                       missing_idxs, blocksize);

    if (ret < 0) {
        log_error("Encountered error in backend decode function!");
        goto out;
    }

    /*
     * Need to fill in the missing data headers so we can generate
     * the original string.
     */
    j = 0;
    while (missing_idxs[j] >= 0) {
        int set_chksum = 1;
        int missing_idx = missing_idxs[j];
        if (missing_idx < k) {
            /* Generate headers */
            char *fragment_ptr = data[missing_idx];
            init_fragment_header(fragment_ptr);
            add_fragment_metadata(instance, fragment_ptr, missing_idx,
                    orig_data_size, blocksize, instance->args.uargs.ct,
                    !set_chksum);
        }
        j++;
    }

    /* Try to generate the original string */
    ret = fragments_to_string(k, m, data, k, out_data, out_data_len);

    if (ret < 0) {
        log_error("Could not convert decoded fragments to a string!");
    }

out:
    /* Free the buffers allocated in prepare_fragments_for_decode */
    if (realloc_bm != 0) {
        for (i = 0; i < k; i++) {
            if (realloc_bm & (1 << i)) {
                free(data[i]);
            }
        }

        for (i = 0; i < m; i++) {
            if (realloc_bm & (1 << (i + k))) {
                free(parity[i]);
            }
        }
    }

    free(data);
    free(parity);
    free(missing_idxs);
    free(data_segments);
    free(parity_segments);

    return ret;
}

/**
 * Reconstruct a missing fragment from a subset of available fragments
 *
 * @param desc - liberasurecode descriptor/handle
 *        from liberasurecode_instance_create()
 * @param fragment_len - size in bytes of the fragments
 * @param available_fragments - erasure encoded fragments
 * @param num_fragments - number of fragments being passed in
 * @param destination_idx - missing idx to reconstruct
 * @param out_fragment - output of reconstruct
 * @return 0 on success, -error code otherwise
 */
int liberasurecode_reconstruct_fragment(int desc,
        char **available_fragments,                     /* input */
        int num_fragments, uint64_t fragment_len,       /* input */
        int destination_idx,                            /* input */
        char* out_fragment)                             /* output */
{
    int ret = 0;
    int blocksize = 0;
    int orig_data_size = 0;
    char **data = NULL;
    char **parity = NULL;
    int *missing_idxs = NULL;
    char *fragment_ptr = NULL;
    int is_destination_missing = 0;
    int k = -1;
    int m = -1;
    int i;
    uint64_t realloc_bm = 0;
    char **data_segments = NULL;
    char **parity_segments = NULL;
    int set_chksum = 1;

    ec_backend_t instance = liberasurecode_backend_instance_get_by_desc(desc);
    if (NULL == instance) {
        ret = -EBACKENDNOTAVAIL;
        goto out;
    }

    if (NULL == available_fragments) {
        log_error("Can not reconstruct fragment, available fragments pointer is NULL");
        ret = -EINVALIDPARAMS;
        goto out;
    }

    if (NULL == out_fragment) {
        log_error("Can not reconstruct fragment, output fragment pointer is NULL");
        ret = -EINVALIDPARAMS;
        goto out;
    }

    k = instance->args.uargs.k;
    m = instance->args.uargs.m;

    for (i = 0; i < num_fragments; i++) {
        /* Verify metadata checksum */
        if (is_invalid_fragment_header(
                (fragment_header_t *) available_fragments[i])) {
            log_error("Invalid fragment header information!");
            ret = -EBADHEADER;
            goto out;
        }
    }

    /*
     * Allocate arrays for data, parity and missing_idxs
     */
    data = alloc_zeroed_buffer(sizeof(char*) * k);
    if (NULL == data) {
        log_error("Could not allocate data buffer!");
        ret = -ENOMEM;
        goto out;
    }

    parity = alloc_zeroed_buffer(sizeof(char*) * m);
    if (NULL == parity) {
        log_error("Could not allocate parity buffer!");
        ret = -ENOMEM;
        goto out;
    }

    missing_idxs = alloc_and_set_buffer(sizeof(int*) * (k + m), -1);
    if (NULL == missing_idxs) {
        log_error("Could not allocate missing_idxs buffer!");
        ret = -ENOMEM;
        goto out;
    }

    /*
     * Separate the fragments into data and parity.  Also determine which
     * pieces are missing.
     */
    ret = get_fragment_partition(k, m, available_fragments, num_fragments,
                                 data, parity, missing_idxs);

    if (ret < 0) {
        log_error("Could not properly partition the fragments!");
        goto out;
    }

    /*
     * Odd corner-case: If the caller passes in a destination_idx that
     * is also included in the available fragments list, we should *not*
     * try to reconstruct.
     *
     * For now, we will log a warning and do nothing.  In the future, we
     * should probably log and return an error.
     *
     */
    i = 0;
    while (missing_idxs[i] > -1) {
        if (missing_idxs[i] == destination_idx) {
            is_destination_missing = 1;
        }
        i++;
    }

    if (!is_destination_missing) {
        if (destination_idx < k) {
            fragment_ptr = data[destination_idx];
        } else {
            fragment_ptr = parity[destination_idx - k];
        }
        log_warn("Dest idx for reconstruction was supplied as available buffer!");
        goto destination_available;
    }

    /*
     * Preparing the fragments for reconstruction.  This will alloc aligned
     * buffers when unaligned buffers were passed in available_fragments.
     * It passes back a bitmap telling us which buffers need to be freed by
     * us (realloc_bm).
     */
    ret = prepare_fragments_for_decode(k, m, data, parity, missing_idxs,
                                       &orig_data_size, &blocksize,
                                       fragment_len, &realloc_bm);
    if (ret < 0) {
        log_error("Could not prepare fragments for reconstruction!");
        goto out;
    }
    data_segments = alloc_zeroed_buffer(k * sizeof(char *));
    parity_segments = alloc_zeroed_buffer(m * sizeof(char *));
    get_data_ptr_array_from_fragments(data_segments, data, k);
    get_data_ptr_array_from_fragments(parity_segments, parity, m);


    /* call the backend reconstruct function passing it desc instance */
    ret = instance->common.ops->reconstruct(instance->desc.backend_desc,
                                            data_segments, parity_segments,
                                            missing_idxs, destination_idx,
                                            blocksize);
    if (ret < 0) {
        log_error("Could not reconstruct fragment!");
        goto out;
    }

    /*
     * Update the header to reflect the newly constructed fragment
     */
    if (destination_idx < k) {
        fragment_ptr = data[destination_idx];
    } else {
        fragment_ptr = parity[destination_idx - k];
    }
    init_fragment_header(fragment_ptr);
    add_fragment_metadata(instance, fragment_ptr, destination_idx,
                          orig_data_size, blocksize, instance->args.uargs.ct,
                          set_chksum);

destination_available:
    /*
     * Copy the reconstructed fragment to the output buffer
     *
     * Note: the address stored in fragment_ptr will be freed below
     */
    memcpy(out_fragment, fragment_ptr, fragment_len);

out:
    /* Free the buffers allocated in prepare_fragments_for_decode */
    if (realloc_bm != 0) {
        for (i = 0; i < k; i++) {
            if (realloc_bm & (1 << i)) {
                free(data[i]);
            }
        }

        for (i = 0; i < m; i++) {
            if (realloc_bm & (1 << (i + k))) {
                free(parity[i]);
            }
        }
    }

    free(data);
    free(parity);
    free(missing_idxs);
    free(data_segments);
    free(parity_segments);

    return ret;
}

/**
 * Return a list of lists with valid rebuild indexes given
 * a list of missing indexes.
 *
 * @desc: liberasurecode instance descriptor (obtained with
 *        liberasurecode_instance_create)
 * @fragments_to_reconstruct list of indexes to reconstruct
 * @fragments_to_exclude list of indexes to exclude from
          reconstruction equation
 * @fragments_needed list of fragments needed to reconstruct
          fragments in fragments_to_reconstruct
 *
 * @return a list of lists (bitmaps) of indexes to rebuild data
 *        from (in 'fragments_needed')
 */
int liberasurecode_fragments_needed(int desc,
                                    int *fragments_to_reconstruct,
                                    int *fragments_to_exclude,
                                    int *fragments_needed)
{
    int ret = 0;

    ec_backend_t instance = liberasurecode_backend_instance_get_by_desc(desc);
    if (NULL == instance) {
        ret = -EBACKENDNOTAVAIL;
        goto out_error;
    }
    if (NULL == fragments_to_reconstruct) {
        log_error("Unable to determine list of fragments needed, pointer to list of indexes to reconstruct is NULL.");
        ret = -EINVALIDPARAMS;
        goto out_error;
    }

    if (NULL == fragments_to_exclude) {
        log_error("Unable to determine list of fragments needed, pointer to list of fragments to exclude is NULL.");
        ret = -EINVALIDPARAMS;
        goto out_error;
    }

    if (NULL == fragments_needed) {
        log_error("Unable to determine list of fragments needed, pointer to list of fragments to reconstruct is NULL.");
        ret = -EINVALIDPARAMS;
        goto out_error;
    }

    /* FIXME preprocessing */

    /* call the backend fragments_needed function passing it desc instance */
    ret = instance->common.ops->fragments_needed(
            instance->desc.backend_desc,
            fragments_to_reconstruct, fragments_to_exclude, fragments_needed);

out_error:
    return ret;
}

/* =~=*=~==~=*=~==~=*=~==~=*=~===~=*=~==~=*=~===~=*=~==~=*=~===~=*=~==~=*=~= */

/**
 * Get opaque metadata for a fragment.  The metadata is opaque to the
 * client, but meaningful to the underlying library.  It is used to verify
 * stripes in verify_stripe_metadata().
 *
 * @param fragment - fragment pointer
 *
 * @param fragment_metadata - pointer to output fragment metadata struct
 *          (reference passed by the user)
 */
int liberasurecode_get_fragment_metadata(char *fragment,
        fragment_metadata_t *fragment_metadata)
{
    int ret = 0;
    fragment_header_t *fragment_hdr = NULL;

    if (NULL == fragment) {
        log_error("Need valid fragment object to get metadata for");
        ret = -EINVALIDPARAMS;
        goto out;
    }

    if (NULL == fragment_metadata) {
        log_error("Need valid fragment_metadata object for return value");
        ret = -EINVALIDPARAMS;
        goto out;
    }

    /* Verify metadata checksum */
    if (is_invalid_fragment_header((fragment_header_t *) fragment)) {
        log_error("Invalid fragment header information!");
        ret = -EBADHEADER;
        goto out;
    }

    memcpy(fragment_metadata, fragment, sizeof(struct fragment_metadata));
    fragment_hdr = (fragment_header_t *) fragment;
    if (LIBERASURECODE_FRAG_HEADER_MAGIC != fragment_hdr->magic) {
        if (LIBERASURECODE_FRAG_HEADER_MAGIC != bswap_32(fragment_hdr->magic)) {
            log_error("Invalid fragment, illegal magic value");
            ret = -EINVALIDPARAMS;
            goto out;
        } else {
            // Must've written this on an opposite-endian architecture.
            // Fix it in fragment_metadata
            fragment_metadata->idx = bswap_32(fragment_metadata->idx);
            fragment_metadata->size = bswap_32(fragment_metadata->size);
            fragment_metadata->frag_backend_metadata_size =
                bswap_32(fragment_metadata->frag_backend_metadata_size);
            fragment_metadata->orig_data_size =
                bswap_64(fragment_metadata->orig_data_size);
            fragment_metadata->chksum_type =
                bswap_32(fragment_metadata->chksum_type);
            for (int i = 0; i < LIBERASURECODE_MAX_CHECKSUM_LEN; i++) {
                fragment_metadata->chksum[i] =
                    bswap_32(fragment_metadata->chksum[i]);
            }
            fragment_metadata->backend_version =
                bswap_32(fragment_metadata->backend_version);
        }
    }

    switch(fragment_metadata->chksum_type) {
        case CHKSUM_CRC32: {
            uint32_t computed_chksum = 0;
            uint32_t stored_chksum = fragment_metadata->chksum[0];
            char *fragment_data = get_data_ptr_from_fragment(fragment);
            uint64_t fragment_size = fragment_metadata->size;
            computed_chksum = crc32(0, (unsigned char *) fragment_data, fragment_size);
            if (stored_chksum != computed_chksum) {
                // Try again with our "alternative" crc32; see
                // https://bugs.launchpad.net/liberasurecode/+bug/1666320
                computed_chksum = liberasurecode_crc32_alt(
                    0, fragment_data, fragment_size);
                if (stored_chksum != computed_chksum) {
                    fragment_metadata->chksum_mismatch = 1;
                } else {
                    fragment_metadata->chksum_mismatch = 0;
                }
            } else {
                fragment_metadata->chksum_mismatch = 0;
            }
            break;
        }
        case CHKSUM_MD5:
            break;
        case CHKSUM_NONE:
        default:
            break;
    }

out:
    return ret;
}

int is_invalid_fragment_header(fragment_header_t *header)
{
    uint32_t csum = 0, metadata_chksum = 0, libec_version = 0;
    assert (NULL != header);
    if (header->libec_version == 0)
        /* libec_version must be bigger than 0 */
        return 1;
    metadata_chksum = header->metadata_chksum;
    libec_version = header->libec_version;
    if (header->magic != LIBERASURECODE_FRAG_HEADER_MAGIC) {
        if (bswap_32(header->magic) != LIBERASURECODE_FRAG_HEADER_MAGIC) {
            log_error("Invalid fragment header (get meta chksum)!");
            return 1;
        } else {
            // Must've written this on an opposite-endian architecture.
            // Fix our reference checksum and version, but *don't touch
            // the header data yet*. Leave that for when we're extracting
            // in liberasurecode_get_fragment_metadata
            metadata_chksum = bswap_32(metadata_chksum);
            libec_version = bswap_32(libec_version);
        }
    }

    if (libec_version < _VERSION(1,2,0))
        /* no metadata checksum support */
        return 0;

    csum = crc32(0, (unsigned char *) &header->meta, sizeof(fragment_metadata_t));
    if (metadata_chksum == csum) {
        return 0;
    }
    // Else, try again with our "alternative" crc32; see
    // https://bugs.launchpad.net/liberasurecode/+bug/1666320
    csum = liberasurecode_crc32_alt(0, &header->meta, sizeof(fragment_metadata_t));
    return (metadata_chksum != csum);
}

int liberasurecode_verify_fragment_metadata(ec_backend_t be,
                                            fragment_metadata_t *md)
{
    int k = be->args.uargs.k;
    int m = be->args.uargs.m;
    if (md->idx > (k + m)) {
        return 1;
    }
    if (md->backend_id != be->common.id) {
        return 1;
    }
    if (!be->common.ops->is_compatible_with(md->backend_version))  {
        return 1;
    }
    return 0;
}

int is_invalid_fragment_metadata(int desc, fragment_metadata_t *fragment_metadata)
{
    ec_backend_t be = liberasurecode_backend_instance_get_by_desc(desc);
    if (!be) {
        log_error("Unable to verify fragment metadata: invalid backend id %d.",
                desc);
        return -EINVALIDPARAMS;
    }
    if (liberasurecode_verify_fragment_metadata(be,
            fragment_metadata) != 0) {
        return -EBADHEADER;
    }
    if (!be->common.ops->is_compatible_with(fragment_metadata->backend_version))  {
        return -EBADHEADER;
    }
    if (fragment_metadata->chksum_mismatch == 1) {
        return -EBADCHKSUM;
    }
    return 0;
}

int is_invalid_fragment(int desc, char *fragment)
{
    uint32_t ver = 0;
    fragment_metadata_t fragment_metadata;
    ec_backend_t be = liberasurecode_backend_instance_get_by_desc(desc);
    if (!be) {
        log_error("Unable to verify fragment metadata: invalid backend id %d.",
                desc);
        return 1;
    }
    if (!fragment) {
        log_error("Unable to verify fragment validity: fragments missing.");
        return 1;
    }
    if (get_libec_version(fragment, &ver) != 0 ||
            ver > LIBERASURECODE_VERSION) {
        return 1;
    }
    if (liberasurecode_get_fragment_metadata(fragment, &fragment_metadata) != 0) {
        return 1;
    }
    if (is_invalid_fragment_metadata(desc, &fragment_metadata) != 0) {
        return 1;
    }
    return 0;
}

int liberasurecode_verify_stripe_metadata(int desc,
        char **fragments, int num_fragments)
{
    int i = 0;
    if (!fragments) {
        log_error("Unable to verify stripe metadata: fragments missing.");
        return -EINVALIDPARAMS;
    }
    if (num_fragments <= 0) {
        log_error("Unable to verify stripe metadata: "
                "number of fragments must be greater than 0.");
        return -EINVALIDPARAMS;
    }

    for (i = 0; i < num_fragments; i++) {
        fragment_metadata_t *fragment_metadata = (fragment_metadata_t*)fragments[i];
        int ret = is_invalid_fragment_metadata(desc, fragment_metadata);
        if (ret < 0) {
            return ret;
        }
    }

    return 0;
}

/* =~=*=~==~=*=~==~=*=~==~=*=~===~=*=~==~=*=~===~=*=~==~=*=~===~=*=~==~=*=~= */

/**
 * This computes the aligned size of a buffer passed into
 * the encode function.  The encode function must pad fragments
 * to be aligned with the word size (w) and the last fragment also
 * needs to be aligned.  This computes the sum of the aligned fragment
 * sizes for a given buffer to encode.
 */
int liberasurecode_get_aligned_data_size(int desc, uint64_t data_len)
{
    int k;
    int ret = 0;
    int word_size;
    int alignment_multiple;

    ec_backend_t instance = liberasurecode_backend_instance_get_by_desc(desc);
    if (NULL == instance) {
        ret = -EBACKENDNOTAVAIL;
        goto out;
    }

    k = instance->args.uargs.k;

    word_size = instance->common.ops->element_size(
            instance->desc.backend_desc) / 8;

    alignment_multiple = k * word_size;

    ret = ((data_len + alignment_multiple - 1) / alignment_multiple)
            * alignment_multiple;

out:
    return ret;
}

/**
 * This will return the minumum encode size, which is the minimum
 * buffer size that can be encoded.
 */
int liberasurecode_get_minimum_encode_size(int desc)
{
    return liberasurecode_get_aligned_data_size(desc, 1);
}

int liberasurecode_get_fragment_size(int desc, int data_len)
{
    ec_backend_t instance = liberasurecode_backend_instance_get_by_desc(desc);
    // TODO: Create a common function to calculate fragment size also for preprocessing
    if (NULL == instance)
        return -EBACKENDNOTAVAIL;
    int aligned_data_len = get_aligned_data_size(instance, data_len);
    int blocksize = aligned_data_len / instance->args.uargs.k;
    int metadata_size = instance->common.ops->get_backend_metadata_size(
                                                instance->desc.backend_desc,
                                                blocksize);
    int size = blocksize + metadata_size;

    return size;
}


/**
 * This will return the liberasurecode version for the descriptor
 */

uint32_t liberasurecode_get_version()
{
    return LIBERASURECODE_VERSION;
}

/* ==~=*=~==~=*=~==~=*=~==~=*=~==~=* misc *=~==~=*=~==~=*=~==~=*=~==~=*=~== */

#if 0
/* Validate backend before calling init */
int liberasurecode_backend_validate(ec_backend_t backend)
{
    /* Verify that the backend implements all required methods */
}

/* FIXME - do we need to use reference counts if we are creating
* a new instance per user */

/* Get a reference to an EC backend */
ec_backend_t liberasurecode_backend_get(const char *name)
{
    ec_backend_t b = liberasurecode_backend_lookup_by_name(name);
    if (NULL != b)
        ++b->users;
    return b;
}

/* Drop an EC backend reference held */
void liberasurecode_backend_put(ec_backend_t backend)
{
    if (backend->users > 0)
        --backend->users;
}

/* Query interface for active instances */
ec_backend_t liberasurecode_backend_instance_active(ec_backend_t instance)
{
    ec_backend_t b;

    SLIST_FOREACH(b, &active_instances, link) {
        if (strcmp(b->name, name) == 0)
            return b;
    }

    return NULL;
}

ec_backend_t liberasurecode_backend_lookup_by_soname(const char *soname)
{
    ec_backend_t b;

    SLIST_FOREACH(b, &active_instances, link) {
        if (strcmp(b->soname, soname) == 0)
            return b;
    }

    return NULL;
}
#endif

/* ==~=*=~==~=*=~==~=*=~==~=*=~==~=*=~==~=*=~==~=*=~==~=*=~==~=*=~==~=*=~== */
