/*
 * Copyright (c) 2013-2019 Cisco Systems, Inc.  All rights reserved
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */

#include "opal_config.h"

#include <stdio.h>
#include <unistd.h>

#include "opal/constants.h"
#include "opal/util/if.h"
#include "opal/util/show_help.h"

#include "btl_usnic_module.h"
#include "btl_usnic_util.h"

// The following comment tells Coverity that this function does not return.
// See https://scan.coverity.com/tune.

/* coverity[+kill] */
void opal_btl_usnic_exit(opal_btl_usnic_module_t *module)
{
    if (NULL == module) {
        /* Find the first module with an error callback */
        for (int i = 0; i < mca_btl_usnic_component.num_modules; ++i) {
            if (NULL != mca_btl_usnic_component.usnic_active_modules
                && NULL != mca_btl_usnic_component.usnic_active_modules[i]
                && NULL != mca_btl_usnic_component.usnic_active_modules[i]->pml_error_callback) {
                module = mca_btl_usnic_component.usnic_active_modules[i];
                break;
            }
        }
        /* If we didn't find a PML error callback, just exit. */
        if (NULL == module) {
            fprintf(stderr, "*** The Open MPI usnic BTL is aborting the MPI job (via exit(3)).\n");
            fflush(stderr);
            exit(1);
        }
    }

    /* After discussion with George, we decided that it was safe to
       cast away the const from opal_proc_local_get() -- the error
       function needs to be smart enough to not take certain actions
       if the passed proc is yourself (e.g., don't call del_procs() on
       yourself). */
    if (NULL != module->pml_error_callback) {
        module
            ->pml_error_callback(&module->super, MCA_BTL_ERROR_FLAGS_FATAL,
                                 (opal_proc_t *) opal_proc_local_get(),
                                 "The usnic BTL is aborting the MPI job (via PML error callback).");
    }

    /* If the PML error callback returns (or if there wasn't one),
       just exit.  Shrug. */
    exit(1);
}

/*
 * Simple utility in a .c file, mainly so that inline functions in .h
 * files don't need to include the show_help header file.
 *
 * The following comment tells Coverity that this function does not
 * return.  See https://scan.coverity.com/tune.  Technically, we
 * shouldn't need this, because opal_btl_usnic_exit() has the same
 * annotation, but let's just help Coverity out.
 */

/* coverity[+kill] */
void opal_btl_usnic_util_abort(const char *msg, const char *file, int line)
{
    opal_show_help("help-mpi-btl-usnic.txt", "internal error after init", true,
                   opal_process_info.nodename, file, line, msg);

    opal_btl_usnic_exit(NULL);
    /* Never returns */
}

void opal_btl_usnic_dump_hex(int verbose_level, int output_id, void *vaddr, int len)
{
    char buf[128];
    size_t bufspace;
    int i, ret;
    char *p;
    uint32_t sum = 0;
    uint8_t *addr;

    addr = vaddr;
    p = buf;
    memset(buf, 0, sizeof(buf));
    bufspace = sizeof(buf) - 1;

    for (i = 0; i < len; ++i) {
        ret = snprintf(p, bufspace, "%02x ", addr[i]);
        p += ret;
        bufspace -= ret;

        sum += addr[i];
        if ((i & 15) == 15) {
            opal_output_verbose(verbose_level, output_id, "%4x: %s\n", i & ~15, buf);

            p = buf;
            memset(buf, 0, sizeof(buf));
            bufspace = sizeof(buf) - 1;
        }
    }
    if ((i & 15) != 0) {
        opal_output_verbose(verbose_level, output_id, "%4x: %s\n", i & ~15, buf);
    }
    /*opal_output_verbose(verbose_level, output_id, "buffer sum = %x\n", sum); */
}

/*
 * Trivial wrapper around snprintf'ing an IPv4 address, with or
 * without a CIDR mask (we don't usually carry around addresses in
 * struct sockaddr form, so this wrapper is marginally easier than
 * using inet_ntop()).
 */
void opal_btl_usnic_snprintf_ipv4_addr(char *out, size_t maxlen, uint32_t addr_be,
                                       uint32_t netmask_be)
{
    int prefixlen;
    uint32_t netmask = ntohl(netmask_be);
    uint32_t addr = ntohl(addr_be);
    uint8_t *p = (uint8_t *) &addr;

    if (netmask != 0) {
        prefixlen = 33 - ffs(netmask);
        snprintf(out, maxlen, "%u.%u.%u.%u/%u", p[3], p[2], p[1], p[0], prefixlen);
    } else {
        snprintf(out, maxlen, "%u.%u.%u.%u", p[3], p[2], p[1], p[0]);
    }
}

/* Pretty-print the given boolean array as a hexadecimal string.  slen should
 * include space for any null terminator. */
void opal_btl_usnic_snprintf_bool_array(char *s, size_t slen, bool a[], size_t alen)
{
    size_t i = 0;
    size_t j = 0;

    /* could accommodate other cases, but not needed right now */
    assert(slen % 4 == 0);

    /* compute one nybble at a time */
    while (i < alen && (j < slen - 1)) {
        unsigned char tmp = 0;

        /* first bool is the leftmost (most significant) bit of the nybble */
        tmp |= !!a[i + 0] << 3;
        tmp |= !!a[i + 1] << 2;
        tmp |= !!a[i + 2] << 1;
        tmp |= !!a[i + 3] << 0;
        tmp += '0';
        s[j] = tmp;

        ++j;
        i += 4;
    }

    s[j++] = '\0';
    assert(i <= alen);
    assert(j <= slen);
}

/* Return the largest size data size that can be packed into max_len using the
 * given convertor.  For example, a 1000 byte max_len buffer may only be able
 * to hold 998 bytes if an indivisible convertor element straddles the 1000
 * byte boundary.
 *
 * This routine internally clones the convertor and does not mutate it!
 */
size_t opal_btl_usnic_convertor_pack_peek(const opal_convertor_t *conv, size_t max_len)
{
    int rc;
    size_t packable_len, position;
    opal_convertor_t temp;

    OBJ_CONSTRUCT(&temp, opal_convertor_t);
    position = conv->bConverted + max_len;
    rc = opal_convertor_clone_with_position(conv, &temp, 1, &position);
    if (OPAL_UNLIKELY(rc < 0)) {
        BTL_ERROR(("unexpected convertor error"));
        abort(); /* XXX */
    }
    assert(position >= conv->bConverted);
    packable_len = position - conv->bConverted;
    OBJ_DESTRUCT(&temp);
    return packable_len;
}
