/*
 * All Rights Reserved (C) 2021-2025
 * Exoligent SARL (Colombes, France)
 * Website: www.exoligent.com
 * Author : Maxime Coroyer <maxime.coroyer@exoligent.com>
 */

#include "sta.h"

int tst_session_start(struct tst_session *sess)
{
    int i, var_cnt, msg_cnt;
    uint8_t station_number = sess->station_number;
    uint8_t station_number_remote = !sess->station_number;
    uint8_t station_bitrate = sess->station_bitrate;
    uint32_t station_tr_ustime = (sess->station_tr_ustime) ? \
        sess->station_tr_ustime : tr_ustime[station_bitrate];
    uint32_t station_ts_ustime = (sess->station_ts_ustime) ? \
        sess->station_ts_ustime : ts_ustime[station_bitrate];
    char *tag_name = (station_number) ? "Station 1" : "Station 0";
    const struct pwrfip_version *lib_version = pwrfip_version_get();
    struct pwrfip_node_cfg node_cfg;

    memset(&node_cfg, 0, sizeof(struct pwrfip_node_cfg));

    /* pwrfip - versions */
    _print(app, info, "test: v%s - pwrfip lib: v%d.%d.%d\n", APP_VERSION,
        lib_version->info.major, lib_version->info.minor, lib_version->info.patch);

    _print(station_number, info, "[fip] node configuration\n");
    /* set node config */
    /* general node configuration */
    node_cfg.fip_phy_addr = station_number;
    node_cfg.fip_seg_num = 0;
    node_cfg.fip_frm_type = PWRFIP_FRM_WORLDFIP;
    node_cfg.fip_bitrate = station_bitrate + 1;
    node_cfg.turn_around_ustime = station_tr_ustime;
    node_cfg.silence_ustime = station_ts_ustime;
    node_cfg.enable_bimedium = 1;
    /* msg configuration */
    node_cfg.msg.enable = 1;
    if (node_cfg.msg.enable) {
        /* set rx messaging sensitivity only for messages destined
         * for segment 0 with DSAP values configured for the node
         */
        node_cfg.msg.rx_segment_tab[0] = PWRFIP_MSG_SEG_ACCEPT_LTD;
        node_cfg.msg.rx_fifo_size = 0; /* 0 to set default value (30) */
        node_cfg.msg.tx_aper_fifo_size = 0; /* default value for tx aper channel:
                                             * tx fifo depth = 24 messages
                                             */
        for (i = 0; i < PWRFIP_MSG_TX_CH_PER_CNT; ++i)
            node_cfg.msg.tx_per_fifo_size[i] = 1; /* minimal value (1) */
        node_cfg.msg.tx_max_repeat = 3; /* 3 tx retries in case of error */
    }
    /* ba configuration (if master active) */
    node_cfg.ba.enable = 1;
    if (node_cfg.ba.enable) {
        /* we calculate startup and election times for BA */
        struct pwrfip_ba_startup_cfg ba_stup_cfg = {
            .mode = PWRFIP_BA_STUP_STANDARD,
            .silence_ustime = ts_ustime[station_bitrate],
            .max_phy_addr = 255,
            .max_prio = 0,
            .my_phy_addr = node_cfg.fip_phy_addr,
            .my_prio = (station_number == 0) ? 0 : 1,
        };
        pwrfip_ba_startup_calculate(&node_cfg.ba.start_ustime,
                &node_cfg.ba.election_ustime, &ba_stup_cfg);
        _print(station_number, info, "[fip] bus arbiter infos\n");
        _print(station_number, info, "   start        : %dus\n",
                node_cfg.ba.start_ustime);
        _print(station_number, info, "   election     : %dus\n",
                node_cfg.ba.election_ustime);
        node_cfg.ba.priority = (station_number == 0) ? 0 /* highest */ : 1;
        node_cfg.ba.msg_req_fifo_size = 0; /* 0 to set default value (60) */
        node_cfg.ba.urgent_var_req_fifo_size = 0; /* 0 to set default value (60) */
        node_cfg.ba.normal_var_req_fifo_size = 0; /* 0 to set default value (60) */
    }
    /* node identification configuration */
    node_cfg.ident.manufacturer_name = "Exoligent\0";
    node_cfg.ident.model_name = "PowerFIP chip\0";
    node_cfg.ident.revision = 0x10; /* v1.0 */
    node_cfg.ident.tag_name = tag_name;
    node_cfg.ident.vendor_field = NULL;

    /* hardware access */
    node_cfg.dev = sess->dev; /* attach pci device to node configuration */

    /* app handlers */
    node_cfg.pwrfip_var_handler = tst_pwrfip_var_handler;
    node_cfg.pwrfip_msg_recv_handler = tst_pwrfip_msg_recv_handler;
    node_cfg.pwrfip_msg_send_handler = tst_pwrfip_msg_send_handler;
    node_cfg.pwrfip_urg_req_handler = NULL;
    node_cfg.pwrfip_nor_req_handler = tst_pwrfip_nor_req_handler;
    node_cfg.pwrfip_ba_state_handler = tst_pwrfip_ba_state_handler;
    node_cfg.pwrfip_ba_sync_handler = tst_pwrfip_ba_sync_handler;
    node_cfg.pwrfip_error_handler = tst_pwrfip_error_handler;
    node_cfg.pwrfip_reset_handler = tst_pwrfip_reset_handler;

    /**
     * User app context
     * note: Useful to retrieve the session context from a pwrfip callback
     */
    node_cfg.user_ctx = (void*) sess;

    _print(station_number, info, "[fip] node init\n");
    sess->node.n = pwrfip_node_init(&node_cfg);
    if (!sess->node.n) {
       _print(station_number, err, "node initialization failed: %s\n",
            pwrfip_strerror(errno));
        return -1;
    }

    print_node_infos(sess->node.n);

    /* create aele (fip node context) */
    sess->node.al = pwrfip_aele_create(sess->node.n);
    if (!sess->node.al) {
        _print(station_number, err, "aele creation failed: %s\n",
            pwrfip_strerror(errno));
        goto fail;
    }

    /* => create the consumed fip sm-mps variables (identification, report,
     *    presence) for the remote node(s) you want to get info about. */
    pwrfip_sm_var_create(sess->node.al, PWRFIP_SM_VAR_IDENT, station_number_remote);
    pwrfip_sm_var_create(sess->node.al, PWRFIP_SM_VAR_REPORT, station_number_remote);
    pwrfip_sm_var_create(sess->node.al, PWRFIP_SM_VAR_PRESENCE, station_number_remote);

    /* => create fip variables */
    var_cnt = (station_number) ? TST_STA1_VAR_COUNT : TST_STA0_VAR_COUNT;
    for (i = 0; i < var_cnt; ++i) {
        struct pwrfip_var_cfg *var_cfg = &tst_var_cfg[station_number][i];
        struct pwrfip_var *var;
        switch (var_cfg->type)
        {
        case PWRFIP_VAR_TYPE_PROD:
            var = pwrfip_var_create(sess->node.al, var_cfg);
            if (!var) {
                _print(station_number, err,
                    "produced variable creation failed: %s\n",
                    pwrfip_strerror(errno));
                goto fail1;
            }
            sess->node.prod_varlist[sess->node.prod_varcnt] = var;
            sess->node.prod_varcnt++;
        break;
        case PWRFIP_VAR_TYPE_CONS:
            var = pwrfip_var_create(sess->node.al, var_cfg);
            if (!var) {
                _print(station_number, err,
                    "consumed variable creation failed: %s\n",
                    pwrfip_strerror(errno));
                goto fail1;
            }
            sess->node.cons_varlist[sess->node.cons_varcnt] = var;
            sess->node.cons_varcnt++;
        break;
        case PWRFIP_VAR_TYPE_SYNC:
            var = pwrfip_var_create(sess->node.al, var_cfg);
            if (!var) {
                _print(station_number, err,
                    "synchro variable creation failed: %s\n",
                    pwrfip_strerror(errno));
                goto fail1;
            }
            sess->node.sync_varlist[sess->node.sync_varcnt] = var;
            sess->node.sync_varcnt++;
        break;
        default: break;
        }
    }

    /* => create fip messages */
    msg_cnt = (station_number) ? TST_STA1_MSG_COUNT : TST_STA0_MSG_COUNT;
    for (i = 0; i < msg_cnt; ++i) {
        struct pwrfip_msg_cfg *msg_cfg = &tst_msg_cfg[station_number][i];
        struct pwrfip_msg *msg;
        switch (msg_cfg->type)
        {
        case PWRFIP_MSG_TYPE_SEND:
            msg = pwrfip_msg_create(sess->node.al, msg_cfg);
            if (!msg) {
                _print(station_number, err,
                    "produced message creation failed: %s\n",
                    pwrfip_strerror(errno));
                goto fail1;
            }
            sess->node.prod_msglist[sess->node.prod_msgcnt] = msg;
            sess->node.prod_msgcnt++;
        break;
        case PWRFIP_MSG_TYPE_RECV:
            msg = pwrfip_msg_create(sess->node.al, msg_cfg);
            if (!msg) {
                _print(station_number, err,
                    "consumed message creation failed: %s\n",
                    pwrfip_strerror(errno));
                goto fail1;
            }
            sess->node.cons_msglist[sess->node.cons_msgcnt] = msg;
            sess->node.cons_msgcnt++;
        break;
        default: break;
        }
    }

    /* create bus arbiter macrocycles */
    for (i = 0; i < TST_MAX_BA_MCYCLE_COUNT; ++i) {
        struct pwrfip_ba_mcycle_cfg *mcycle_cfg = &tst_ba_mcycle_cfg[i];
        struct pwrfip_ba_mcycle *mcycle;

        mcycle = pwrfip_ba_mcycle_create(sess->node.n, mcycle_cfg);
        if (!mcycle) {
            _print(station_number, err, "macrocycle creation failed: %s\n",
                pwrfip_strerror(errno));
            goto fail1;
        }
        sess->node.mc_list[sess->node.mc_cnt] = mcycle;
        sess->node.mc_cnt++;
    }

    /* attach to the session the lists of aperiodic var identifiers
     * to request alternatively */
    sess->node.varids_list_to_send = true; /* initial condition */
    for (i = 0; i < TST_MAX_VARIDS_LIST_COUNT; ++i)
        sess->node.varids_list[i] = &tst_varids_list[i];

    _print(station_number, info, "[fip] node start\n");
    if (pwrfip_node_start(sess->node.al, &sess->node.mc_list[0],
            sess->node.mc_cnt)) {
        _print(station_number, err, "node starting failed: %s\n",
            pwrfip_strerror(errno));
        goto fail2;
    }

    if (node_cfg.ba.enable) {
        _print(station_number, info, "[fip] ba start\n");
        if (pwrfip_ba_start(sess->node.mc_list[0])) {
            _print(station_number, err, "ba macrocycle starting failed: %s\n",
                pwrfip_strerror(errno));
            goto fail2;
        }
    }

    if (node_cfg.msg.enable) {
        /**
         * Message app context initialization
         */
        if (station_number) {
            /* station 1 */
            /* initialization of random number generator */
            /* note: used for output message */
            srand(time(NULL));
        } else {
            /* station 0 */
            /* node 0 initiates a message exchange with node 1
             * as soon as possible */
            sess->node.prod_msgtmo = 0;
        }
    }

    return 0;

fail2: /* ba mcycle deletion. pwrfip_node_exit does the job also */
    for (i = 0; i < sess->node.mc_cnt; ++i)
        pwrfip_ba_mcycle_delete(sess->node.mc_list[i]);
fail1: /* var+aele deletion. pwrfip_node_exit does the job also */
    for (i = 0; i < sess->node.cons_varcnt; ++i)
        pwrfip_var_delete(sess->node.cons_varlist[i]);

    for (i = 0; i < sess->node.prod_varcnt; ++i)
        pwrfip_var_delete(sess->node.prod_varlist[i]);

    for (i = 0; i < sess->node.sync_varcnt; ++i)
        pwrfip_var_delete(sess->node.sync_varlist[i]);

    for (i = 0; i < sess->node.cons_msgcnt; ++i)
        pwrfip_msg_delete(sess->node.cons_msglist[i]);

    for (i = 0; i < sess->node.prod_msgcnt; ++i)
        pwrfip_msg_delete(sess->node.prod_msglist[i]);

    pwrfip_aele_delete(sess->node.al);
fail:
    pwrfip_node_exit(sess->node.n);
    return -1;
}

int tst_session_stop(struct tst_session* sess)
{
    uint8_t station_number = sess->station_number;

    /* print node report */
    tst_node_report(sess);

    /* print session stats */
    _print(station_number, info, "** session stats:\n");
    _print(station_number, info,
        "  mcycle_counter: %"PRIu64"\n", sess->stats.nb_sync_cb);

    if (sess->stats.nb_sync_cb) {
        uint64_t avg_duration = ((float) sess->stats.cumul_sync_cb_duration) \
                    / sess->stats.nb_sync_cb;
        _print(station_number, info,
            "    - occurrence period  (us): min=%"PRIu64", max=%"PRIu64"\n",
            sess->stats.min_sync_cb_last,
            sess->stats.max_sync_cb_last);
        _print(station_number, info,
            "    - treatment duration (us): min=%"PRIu64", avg=%"PRIu64", max=%"PRIu64"\n",
            sess->stats.min_sync_cb_duration, avg_duration,
            sess->stats.max_sync_cb_duration);
        _print(station_number, info, "  msg:\n");
        _print(station_number, info, "    - send : ok=%"PRIu64" [warn=%"PRIu64"]\n",
            sess->stats.tx_msg_ok, sess->stats.tx_msg_warn);
        _print(station_number, info, "    - recv : ok=%"PRIu64" [warn=%"PRIu64"]\n",
            sess->stats.rx_msg_ok, sess->stats.rx_msg_warn);
        _print(station_number, info, "  var:\n");
        _print(station_number, info, "    - write: ok=%"PRIu64" [warn=%"PRIu64"]\n",
            sess->stats.w_var_ok, sess->stats.w_var_warn);
        _print(station_number, info, "    - read : ok=%"PRIu64" [warn=%"PRIu64"]\n",
            sess->stats.r_var_ok, sess->stats.r_var_warn);
        _print(station_number, info, "    - ping/\n");
        _print(station_number, info, "      pong : ok=%"PRIu64" [nok=%"PRIu64"]\n",
            sess->stats.pp_ok, sess->stats.pp_nok);
    }

    /* stop the node and delete all resources attached (aele, var, mcycle..) */
    _print(station_number, info, "[fip] node stop\n");
    if (pwrfip_node_exit(sess->node.n)) {
        _print(station_number, err, "node exiting failed: %s\n",
            pwrfip_strerror(errno));
        return -1;
    }
    _print(station_number, info, "[fip] node exit ok\n");
    return 0;
}

int tst_node_report(struct tst_session* sess)
{
    uint8_t station_number = sess->station_number;
    uint16_t fd_status;
    struct pwrfip_node_report report;

    if (pwrfip_node_report_get(sess->node.n, &report)) {
        _print(station_number, err, "node report: %s\n",
            pwrfip_strerror(errno));
        return -1;
    }

// #if defined(_WIN32) || defined(_WIN64)
//    system("cls");
// #else
//    system("clear");
// #endif
    fd_status = report.medium_status.state;
    _print(station_number, info, "** local report:\n");
    _print(station_number, info, "  node_state    : %d (%s)\n",
        report.node_status.state, pwrfip_node_state_str[report.node_status.state]);
    _print(station_number, info, "  node_op       : %d (%s)\n",
        report.node_status.op, pwrfip_node_op_str[report.node_status.op]);
    _print(station_number, info, "  ba_state      : %d (%s)\n",
        report.ba_status.state, pwrfip_ba_state_str[report.ba_status.state]);
    _print(station_number, info, "  ba_window     : %d (%s)\n",
        report.ba_status.window, pwrfip_ba_wind_str[report.ba_status.window]);
    _print(station_number, info, "  medium_state  : 0x%04x\n", fd_status);
    _print(station_number, info, "    [channel 1]\n");
    _print(station_number, info, "      enable    : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH1_VALID) ? "yes": "no");
    _print(station_number, info, "      tx_err    : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH1_TX_ERROR) ? "yes": "no");
    _print(station_number, info, "      watchdog  : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH1_WATCHDOG) ? "yes": "no");
    _print(station_number, info, "    [channel 2]\n");
    _print(station_number, info, "      enable    : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH2_VALID) ? "yes": "no");
    _print(station_number, info, "      tx_err    : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH2_TX_ERROR) ? "yes": "no");
    _print(station_number, info, "      watchdog  : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH2_WATCHDOG) ? "yes": "no");
    _print(station_number, info, "  tx_err        :\n");
    _print(station_number, info, "    ok          : %d\n", report.tx_err.ok);
    _print(station_number, info, "    collision   : %d\n", report.tx_err.collision);
    _print(station_number, info, "    consistency : %d\n", report.tx_err.consistency);
    _print(station_number, info, "    not_fresh   : %d\n", report.tx_err.not_fresh);
    _print(station_number, info, "  rx_err        :\n");
    _print(station_number, info, "    ok          : %d\n", report.rx_err.ok);
    _print(station_number, info, "    pre_mis     : %d\n", report.rx_err.pre_mis);
    _print(station_number, info, "    fsd_mis     : %d\n", report.rx_err.fsd_mis);
    _print(station_number, info, "    fsd_unk     : %d\n", report.rx_err.fsd_unk);
    _print(station_number, info, "    fed_mis     : %d\n", report.rx_err.fed_mis);
    _print(station_number, info, "    crc_bad     : %d\n", report.rx_err.crc_bad);
    _print(station_number, info, "    pdu_bad     : %d\n", report.rx_err.pdu_bad);
    _print(station_number, info, "    len_bad     : %d\n", report.rx_err.len_bad);
    _print(station_number, info, "    not_fresh   : %d\n", report.rx_err.not_fresh);
    _print(station_number, info, "    not_prompt  : %d\n", report.rx_err.not_prompt);
    return 0;
}

void tst_pwrfip_var_handler(struct pwrfip_node *node,
        struct pwrfip_var *var, struct pwrfip_event *event)
{
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    msg_src src = (sess->station_number == 1) ? 1 : 0;
    _print(src, evt, "[general handler] VAR [-> id=0x%04x]\n", var->id);
}

void tst_pwrfip_msg_recv_handler(struct pwrfip_node *node,
                struct pwrfip_msg *msg, struct pwrfip_event *event)
{
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    msg_src src = (sess->station_number == 1) ? 1 : 0;
    _print(src, evt,
        "[general handler] RX MSG [-> header: src=0x%08x dst=0x%08x]\n",
        msg->hdr.src.addr, msg->hdr.dst.addr);
}

void tst_pwrfip_msg_send_handler(struct pwrfip_node *node,
                struct pwrfip_msg *msg, struct pwrfip_event *event)
{
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    msg_src src = (sess->station_number == 1) ? 1 : 0;
    _print(src, evt,
        "[general handler] TX MSG [-> header: src=0x%08x dst=0x%08x]\n",
        msg->hdr.src.addr, msg->hdr.dst.addr);
}

void tst_pwrfip_nor_req_handler(struct pwrfip_node *node,
        struct pwrfip_event *event)
{
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    // msg_src src = (sess->station_number == 1) ? 1 : 0;
    // char *req_prio_str;
    // switch (event->code) {
    // case PWRFIP_EVT_SEND_APU: req_prio_str = "urgent"; break;
    // case PWRFIP_EVT_SEND_APN: req_prio_str = "normal"; break;
    // default: req_prio_str = "unknown"; break;
    // }
    // _print(src, evt, "aperiodic variables request sent! [prio: %s]\n",
    //     req_prio_str);

    if (sess->node.varids_list_idx == TST_VARIDS_LIST_SMMPS_INFO) {
        /**
         * the sm_mps information request list has been sent on the fip network
         * => we display the information of the present nodes on network.
         */
        // print_sm_mps_infos(node);
    }

    /* prepare the next list of aper var to request */
    sess->node.varids_list_idx = \
        (sess->node.varids_list_idx  + 1) % TST_MAX_VARIDS_LIST_COUNT;
    sess->node.varids_list_to_send = true;
}

void tst_pwrfip_ba_state_handler(struct pwrfip_node *node,
        struct pwrfip_event *event)
{
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    msg_src src = (sess->station_number == 1) ? 1 : 0;
    char *ba_state_str;
    switch (event->code) {
    case PWRFIP_EVT_BA_ACTIVITY:
        /* channel purge +
         * forces here a request for a list of aperiodic variables
         */
        pwrfip_var_aper_channel_purge(node, PWRFIP_VAR_APER_CH_NORMAL,
            NULL, NULL);
        sess->node.varids_list_to_send = true;
        ba_state_str = "active";
    break;
    case PWRFIP_EVT_BA_STOP_TMO:
    case PWRFIP_EVT_BA_STOP_ERR:
    case PWRFIP_EVT_BA_STOP_USR: ba_state_str = "stopped"; break;
    case PWRFIP_EVT_BA_IDLE:     ba_state_str = "idle"; break;
    default: ba_state_str = "unknown"; break;
    }
    _print(src, evt, "BA_STATE [=> %s]\n", ba_state_str);
}

void tst_pwrfip_ba_sync_handler(struct pwrfip_node *node,
        struct pwrfip_var *var, struct pwrfip_event *event)
{
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    int i;
    msg_src src = (sess->station_number == 1) ? 1 : 0;
    unsigned long last_us = 0;
    static int is_first = 1;
    uint16_t pong_id = (sess->station_number == 1) ? 0x3800 : 0x3801;
    enum pwrfip_ba_state ba_state = PWRFIP_BA_STATE_INITIAL;
#if defined(__QNX__) || defined(_WIN32) || defined(_WIN64)
    static uint64_t start_treatment, stop_treatment, last_treatment = 0;
#else
    static struct timeval start_treatment, stop_treatment,
            last_treatment = { .tv_sec = 0, .tv_usec = 0, };
#endif

    /**
     * We avoid logging the warnings at app startup and shutdown:
     * - at startup: to give the remote node time to write its variables
     *   at least once.
     * - at shutdown: to avoid not significant warns.
     */
    int log_warn = (!running || is_first) ? 0 : 1;

    _gettime(&start_treatment);

    /* get bus arbiter status */
    pwrfip_ba_status_get(sess->node.n, &sess->node.ba_status);
    ba_state = sess->node.ba_status.state;

    /**
     * The payload (64-bit counter) of FIP variables is updated
     * here with the following ping/pong principle:
     * Example:
     *            sta0(master)      -        sta1(slave)
     *
     * (w_cnt+1) --> 0x3800 (P) ----------> 0x3800 (C) ---
     *                                                    | (r_cnt)
     *   (r_cnt) <-- 0x3801 (C) ----------> 0x3801 (P) <--
     *
     * => Master node checks: (r_cnt_prev + 1) == r_cnt
     *
     * Note1: P: prod var, C: cons var
     * Note2: The writing counter is incremented by the master node (BA)
     */

    /* read loop for input variables */
    for (i = 0; i < sess->node.cons_varcnt; ++i) {
        struct pwrfip_var *var = sess->node.cons_varlist[i];

        if (pwrfip_var_read(var)) {
            _print(src, err, "r_var[0x%04x] failed: %s\n", var->id,
                pwrfip_strerror(errno));
            /* we consider this error as fatal error; so we
             * stop the test */
            running = 0;
            return;
        }
        if (var->error) {
            const char *err_str = (var->error < _PWRFIP_VAR_ERR_MAX) ? \
                    pwrfip_var_err_str[var->error] : "unknown error";
            if (log_warn) {
                _print(src, warn, "r_var[0x%04x] state: %s\n",
                    var->id, err_str);
                sess->stats.r_var_warn++;
            }
            continue;
        }
        sess->stats.r_var_ok++;

        memcpy(&sess->node.r_counter_list[i], var->buffer, sizeof(uint64_t));

        if (var->id == pong_id) {
            if (sess->node.r_counter_prev) {
                int ok = ((sess->node.r_counter_prev + 1) == \
                           sess->node.r_counter_list[i]) ? 1 : 0;
                if (ok)
                    sess->stats.pp_ok++;
                else {
                    uint64_t mcycles_lost = sess->node.r_counter_list[i];
                    if (sess->node.r_counter_prev <= sess->node.r_counter_list[i])
                        mcycles_lost -= sess->node.r_counter_prev;
                    sess->stats.pp_nok += mcycles_lost;
                    if (mcycles_lost)
                        _print(src, warn,
                            "ping/pong state: mcycles_lost=%"PRIu64"\n",
                            mcycles_lost);
                }
            }
            sess->node.r_counter_prev = sess->node.r_counter_list[i];
        }
    }

    /* write loop for output variables */
    for (i = 0; i < sess->node.prod_varcnt; ++i) {
        struct pwrfip_var *var = sess->node.prod_varlist[i];
        int size = (var->bsz < sizeof(uint64_t)) ? var->bsz : sizeof(uint64_t);
        uint64_t *counter = (ba_state == PWRFIP_BA_STATE_RUNNING) ? \
                &sess->node.w_counter_list[i] : &sess->node.r_counter_list[i];

        /* update payload value */
        if (ba_state == PWRFIP_BA_STATE_RUNNING)
            sess->node.w_counter_list[i]++;
        memcpy(var->buffer, counter, size);

        if (pwrfip_var_write(var)) {
            _print(src, err, "w_var[0x%04x] failed: %s\n", var->id,
                pwrfip_strerror(errno));
            /* we consider this error as fatal error; so we
             * stop the test */
            running = 0;
            return;
        }
        if (var->error) {
            const char *err_str = (var->error < _PWRFIP_VAR_ERR_MAX) ? \
                    pwrfip_var_err_str[var->error] : "unknown error";
            if (log_warn) {
                _print(src, warn, "w_var[0x%04x] state: %s\n",
                    var->id, err_str);
                sess->stats.w_var_warn++;
            }
            continue;
        }
        sess->stats.w_var_ok++;
    }

    /* aperiodic variable requests
     * note: supported by station 0 only
     */
    if (sess->station_number == 0 &&
        sess->node.varids_list_to_send) {
        uint8_t channel_error = 0;
        struct tst_varids *varids_list = \
            sess->node.varids_list[sess->node.varids_list_idx];

        sess->node.varids_list_to_send = false;

        if (pwrfip_varidlist_aper_request(node, varids_list->ids,
                varids_list->cnt, PWRFIP_VAR_APER_CH_NORMAL, &channel_error)) {
            _print(src, err, "var_aper_request failed: %s\n",
                pwrfip_strerror(errno));
            /* we consider this error as fatal error; so we
             * stop the test */
            running = 0;
            return;
        }
        if (channel_error) {
            const char *err_str = (channel_error < _PWRFIP_VAR_ERR_MAX) ?
                    pwrfip_var_err_str[channel_error] : "unknown error";
            if (log_warn)
                _print(src, warn, "var_aper_request state: %s\n", err_str);
        }
    }

    _gettime(&stop_treatment);
    last_us = (is_first) ? 0 : get_elapsed_us(start_treatment, last_treatment);
    is_first = 0;
    last_treatment = start_treatment;

    /**
     * Update session stats
     */
    /* => min/max interval between two network events (synch. var) */
    if (last_us)
        update_min_max(last_us, &sess->stats.min_sync_cb_last,
            &sess->stats.max_sync_cb_last);
    /* => event treatment duration */
    update_stats(&start_treatment, &stop_treatment, &sess->stats.nb_sync_cb,
        &sess->stats.cumul_sync_cb_duration,
        &sess->stats.min_sync_cb_duration,
        &sess->stats.max_sync_cb_duration);
}

void tst_pwrfip_error_handler(struct pwrfip_node *node,
        enum pwrfip_error_code code)
{
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    msg_src src = (sess->station_number == 1) ? 1 : 0;
    if (running)
        _print(src, err,
            "error_handler: %s (err_code=%d)\n",
            pwrfip_strerror(code),
            code);
    running = 0;
}

void tst_pwrfip_reset_handler(struct pwrfip_node *node)
{
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    msg_src src = (sess->station_number == 1) ? 1 : 0;
    if (pwrfip_device_reset(node->infos->cfg.dev)) {
        _print(src, err, "reset failed: %s\n", pwrfip_strerror(errno));
        return;
    }
    _print(src, evt, "reset component (powerfip)\n");
}

void tst_pwrfip_msg_recv_spec_handler(struct pwrfip_node *node,
        struct pwrfip_msg *cons_msg, struct pwrfip_event *event)
{
    const char *err_str = (cons_msg->error < _PWRFIP_MSG_ERR_MAX) ? \
        pwrfip_msg_err_str[cons_msg->error] : "unknown error";
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    uint8_t sta_num = sess->station_number;
    struct pwrfip_msg *prod_msg = sess->node.prod_msglist[0];
    char my_msg[256];

    /**
     * We check if the message has been correctly received
     */
    if (cons_msg->error) {
        /* error detected: report a warning here */
        _print(sta_num, warn, "RX MSG: %s\n", err_str);
        sess->stats.rx_msg_warn++;
    } else {
        /* message correctly received */
        // _print(sta_num, evt, "RX MSG [-> header: src=0x%08x dst=0x%08x]\n",
        //     cons_msg->hdr.src.addr, cons_msg->hdr.dst.addr);
        // _print(sta_num, evt, "RX MSG: %s\n", cons_msg->buffer);
        sess->stats.rx_msg_ok++;
    }

    memset(my_msg, 0, sizeof(my_msg));

    switch (sta_num) {
    case 0:
    {
        /**
         * Node0 received a new message.
         * The node checks if the message comes from node 1. If so,
         * it extracts the random number from the message, and determines
         * if it is even or odd.
         */
        int number = 0;
        const char *sep = " "; /* separator */
        char *token;
        if (cons_msg->hdr.src.addr != 0x0C0100) {
            /* remote address doesn't correspond to a message from node 1 */
            _print(sta_num, warn, "unknown received message\n");
            break;
        }
        /* extract number */
        strncpy(my_msg, (char*) cons_msg->buffer, cons_msg->bsz);
        token = strtok(my_msg, sep); /* token <=> "sta1" */
        token = strtok(NULL, sep); /* token <=> "says:" */
        token = strtok(NULL, sep); /* token <=> "is" */
        token = strtok(NULL, sep); /* token <=> random number */
        number = atoi(token);
        /* odd/even checking */
        sprintf(my_msg, "sta0 says: %d is %s", number,
            (number % 2) ? "odd" : "even");
        prod_msg->bsz = (strlen(my_msg) > 256) ? 256 : strlen(my_msg);
        strncpy((char*) prod_msg->buffer, my_msg, prod_msg->bsz);
        /* we send the answer odd/even result to node 1 */
        pwrfip_msg_send(prod_msg);
    } break;

    case 1:
    {
        /**
         * Node1 received a new message.
         * The node checks if the message comes from node 0. If so,
         * it sends a new random number.
         */
        if (cons_msg->hdr.src.addr != 0x0C0000) {
            /* remote address doesn't correspond to a message from node 0 */
            _print(sta_num, warn, "unknown received message\n");
            break;
        }
        sprintf(my_msg, "sta1 says: is %d even or odd?", rand() % 256);
        prod_msg->bsz = (strlen(my_msg) > 256) ? 256 : strlen(my_msg);
        strncpy((char*) prod_msg->buffer, my_msg, prod_msg->bsz);
        /* we send a new random value to node 0 */
        pwrfip_msg_send(prod_msg);
    } break;
    default: break;
    }
}

void tst_pwrfip_msg_send_spec_handler(struct pwrfip_node *node,
        struct pwrfip_msg *prod_msg, struct pwrfip_event *event)
{
    const char *err_str = (prod_msg->error < _PWRFIP_MSG_ERR_MAX) ? \
        pwrfip_msg_err_str[prod_msg->error] : "unknown error";
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    uint8_t sta_num = sess->station_number;

    /* a tx msg activity occured, we ream the timeout */
    if (sta_num == 0)
        sess->node.prod_msgtmo = 4; /* 4 x 500ms */

    /**
     * We check if the message has been correctly sent
     */
    if (prod_msg->error) {
        /* error detected: report a warning here */
        _print(sta_num, warn, "TX MSG: %s\n", err_str);
        sess->stats.tx_msg_warn++;
    } else {
        /* message correctly sent */
        // _print(sta_num, evt, "TX MSG [-> header: src=0x%08x dst=0x%08x]\n",
        //     prod_msg->hdr.src.addr, prod_msg->hdr.dst.addr);
        // _print(sta_num, evt, "TX MSG: %s\n", prod_msg->buffer);
        sess->stats.tx_msg_ok++;
    }
}

void print_sm_mps_infos(struct pwrfip_node *node)
{
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    msg_src src = (sess->station_number == 1) ? 1 : 0;
    struct pwrfip_sm_presence_list pres_list;
    struct pwrfip_sm_ba_sync ba_sync;
    struct pwrfip_sm_identification ident;
    struct pwrfip_sm_report report;
    struct pwrfip_sm_presence pres;
    int i;

#if defined(_WIN32) || defined(_WIN64)
   system("cls");
#else
   system("clear");
#endif

    if (pwrfip_sm_ba_sync_get(node, &ba_sync)) {
        _print(src, err, "sm_ba_sync_get failed: %s\n",
            pwrfip_strerror(errno));
        return;
    }
    if (pwrfip_sm_presence_list_get(node, &pres_list)) {
        _print(src, err, "sm_presence_list_get failed: %s\n",
            pwrfip_strerror(errno));
        return;
    }

    _print(src, info, "[fip] ba on network      : node_addr=0x%02x (mcycle_num=%d)\n",
        ba_sync.addr, ba_sync.mcycle_num);

    _print(src, info, "[fip] nodes list         : %d node(s)\n", pres_list.count);
    for (i = 0; i < pres_list.count; ++i)
    {
        uint8_t node_addr = pres_list.addr[i];
        _print(src, info, "  [node 0x%02x]\n", node_addr);

        if (pwrfip_sm_identification_get(node, node_addr, &ident)) {
            _print(src, err, "sm_identification_get failed: %s\n",
                pwrfip_strerror(errno));
            continue;
        }
        if (pwrfip_sm_report_get(node, node_addr, &report)) {
            _print(src, err, "sm_report_get failed: %s\n",
                pwrfip_strerror(errno));
        }
        if (pwrfip_sm_presence_get(node, node_addr, &pres)) {
            _print(src, err, "sm_presence_get failed: %s\n",
                pwrfip_strerror(errno));
        }

        /* => identification */
        _print(src, info, "    - identification\n");
        _print(src, info, "      + manufacturer_name: %s\n",
            ident.manufacturer_name);
        _print(src, info, "      + model_name       : %s\n",
            ident.model_name);
        _print(src, info, "      + revision         : 0x%02x\n",
            ident.revision);
        _print(src, info, "      + tag_name         : %s\n",
            ident.tag_name);
        _print(src, info, "      + smmps_class      : 0x%02x\n",
            ident.smmps_class);
        _print(src, info, "      + vendor_field     : %s\n",
            ident.vendor_field);
        /* => report */
        _print(src, info, "    - report\n");
        _print(src, info, "      + rx_ok_ch1        : %d\n",
            report.rx_ok_ch1);
        _print(src, info, "      + rx_ok_ch2        : %d\n",
            report.rx_ok_ch2);
        _print(src, info, "      + rx_nok_ch1       : %d\n",
            report.rx_nok_ch1);
        _print(src, info, "      + rx_nok_ch2       : %d\n",
            report.rx_nok_ch2);
        _print(src, info, "      + channel_status   : 0x%04x\n",
            report.channel_status.value);
        _print(src, info, "        [channel 1]      : %s\n",
            report.channel_status.summary_ch1 ? "ok" : "nok");
        _print(src, info, "          + tx/rx quality: %s/%s\n",
            report.channel_status.tx_quality_ch1 ? "ok" : "nok",
            report.channel_status.rx_quality_ch1 ? "ok" : "nok");
        _print(src, info, "          + validity     : %s\n",
            report.channel_status.valid_ch1 ? "ok" : "nok");
        _print(src, info, "          + traffic      : %s\n",
            report.channel_status.traffic_ch1 ? "ok" : "nok");
        _print(src, info, "        [channel 2]      : %s\n",
            report.channel_status.summary_ch2 ? "ok" : "nok");
        _print(src, info, "          + tx/rx quality: %s/%s\n",
            report.channel_status.tx_quality_ch2 ? "ok" : "nok",
            report.channel_status.rx_quality_ch2 ? "ok" : "nok");
        _print(src, info, "          + validity     : %s\n",
            report.channel_status.valid_ch2 ? "ok" : "nok");
        _print(src, info, "          + traffic      : %s\n",
            report.channel_status.traffic_ch2 ? "ok" : "nok");;
        /* => presence */
        _print(src, info, "    - presence\n");
        _print(src, info, "      + ident_len        : %d\n",
            pres.ident_len);
        _print(src, info, "      + ba_state         : %s\n",
            pwrfip_ba_state_str[pres.ba_state]);
        _print(src, info, "      + ba_prio          : %d\n",
            pres.ba_prio);
    }
}

void print_node_infos(struct pwrfip_node *node)
{
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    msg_src src = (sess->station_number == 1) ? 1 : 0;
    struct pwrfip_node_infos *inf = node->infos;

    _print(src, info, "[fip] node infos\n");
    _print(src, info, "   cpu_id       : 0x%" PRIx64 "\n",
        inf->cpu_id);
    _print(src, info, "   bss_bsz      : %dB [max_bsz: %dB]\n", inf->bss_bsz,
        inf->bss_max_bsz);
    _print(src, info,
        "   gateware     : v%d.%d.%d [build date: %02d/%02d/%02d]\n",
        inf->gw_version.info.major,
        inf->gw_version.info.minor,
        inf->gw_version.info.patch,
        inf->gw_version.date.info.month,
        inf->gw_version.date.info.day,
        inf->gw_version.date.info.year);
    _print(src, info,
        "   firmware     : v%d.%d.%d [build date: %02d/%02d/%02d]\n",
        inf->fw_version.info.major,
        inf->fw_version.info.minor,
        inf->fw_version.info.patch,
        inf->fw_version.date.info.month,
        inf->fw_version.date.info.day,
        inf->fw_version.date.info.year);
    _print(src, info,
        "   driver       : v%d.%d.%d [build date: %02d/%02d/%02d]\n",
        inf->drv_version.info.major,
        inf->drv_version.info.minor,
        inf->drv_version.info.patch,
        inf->drv_version.date.info.month,
        inf->drv_version.date.info.day,
        inf->drv_version.date.info.year);
    _print(src, info,
        "   library      : v%d.%d.%d [build date: %02d/%02d/%02d]\n",
        inf->lib_version.info.major,
        inf->lib_version.info.minor,
        inf->lib_version.info.patch,
        inf->lib_version.date.info.month,
        inf->lib_version.date.info.day,
        inf->lib_version.date.info.year);
}
