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

#include "common.h"
#include "sta.h"

int tst_session_start(struct tst_session *sess)
{
    int i;
    msg_src src = (sess->station_ismstr) ? ping : pong;
    uint8_t station_number = 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];
    const struct pwrfip_version *lib_version = pwrfip_version_get();
    struct pwrfip_node_cfg node_cfg;
    struct pwrfip_var_cfg var_cfg;
    struct pwrfip_var *var;

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

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

    _print(src, 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 => disable */
    node_cfg.msg.enable = 0;
    /* ba configuration (if master active) */
    node_cfg.ba.enable = (sess->station_ismstr) ? 1 : 0;
    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(src, info, "[fip] bus arbiter infos\n");
        _print(src, info, "   start        : %dus\n",
                node_cfg.ba.start_ustime);
        _print(src, 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 = NULL;
    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 = NULL;
    node_cfg.pwrfip_msg_recv_handler = NULL; /* no fip rx msg to treat */
    node_cfg.pwrfip_msg_send_handler = NULL; /* no fip tx msg to treat */
    node_cfg.pwrfip_urg_req_handler = NULL; /* no urgent aper. var request to treat */
    node_cfg.pwrfip_nor_req_handler = NULL;
    node_cfg.pwrfip_ba_state_handler = tst_pwrfip_ba_state_handler;
    node_cfg.pwrfip_ba_sync_handler = NULL;
    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(src, info, "[fip] node init\n");
    sess->node.n = pwrfip_node_init(&node_cfg);
    if (!sess->node.n) {
       _print(src, 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(src, err, "aele creation failed: %s\n",
            pwrfip_strerror(errno));
        goto fail;
    }

    /* => create fip variables */
    /*      -> build produced varlist */
    memset(&var_cfg, 0, sizeof(struct pwrfip_var_cfg));
    var_cfg.type = PWRFIP_VAR_TYPE_PROD;
    var_cfg.id = (node_cfg.ba.enable) ? 0x0500 : 0x0600;
    var_cfg.id |= sess->ping_addr;
    var_cfg.prod.payload_bsz = sess->varlen;
    var_cfg.prod.flags = PWRFIP_VAR_FLAGS_REFRESH; /* enable prod status */
    var_cfg.prod.refreshment_ustime = \
        1.5 * sess->ba_cycle_ustime; /* 1.5 cycle time */
    var_cfg.prod.evt_type = PWRFIP_EVT_TYPE_NONE;
    var_cfg.pwrfip_var_handler = NULL;
    var = pwrfip_var_create(sess->node.al, &var_cfg);
    if (!var) {
        _print(src, 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++;
    /*      -> build consumed varlist */
    memset(&var_cfg, 0, sizeof(struct pwrfip_var_cfg));
    var_cfg.type = PWRFIP_VAR_TYPE_CONS;
    var_cfg.id = (node_cfg.ba.enable) ? 0x0600 : 0x0500;
    var_cfg.id |= sess->ping_addr;
    var_cfg.cons.payload_bsz = sess->varlen;
    var_cfg.cons.flags = \
        /* enable prod status */
        PWRFIP_VAR_FLAGS_REFRESH | \
        /* enable promptness checking */
        PWRFIP_VAR_FLAGS_PROMPT | \
        /* enable pdu + len bytes checking */
        PWRFIP_VAR_FLAGS_CHK_PDU_LEN;
    var_cfg.cons.promptness_ustime = \
        1.5 * sess->ba_cycle_ustime; /* 1.5 cycle time */
    var_cfg.cons.evt_type = PWRFIP_EVT_TYPE_PERMANENT;
    var_cfg.pwrfip_var_handler = tst_pwrfip_sync_rw_op_handler;
    var = pwrfip_var_create(sess->node.al, &var_cfg);
    if (!var) {
        _print(src, 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++;

    /* create bus arbiter macrocycle */
    if (node_cfg.ba.enable) {
        struct pwrfip_ba_mcycle_cfg mcycle_cfg;
        struct pwrfip_ba_wind_cfg ba_wind_cfg[2];
        struct pwrfip_ba_request ba_req[2];
        struct pwrfip_ba_mcycle *mcycle;

        memset(&mcycle_cfg, 0, sizeof(struct pwrfip_ba_mcycle_cfg));
        memset(&ba_wind_cfg, 0, 2 * sizeof(struct pwrfip_ba_wind_cfg));
        memset(&ba_req, 0, 2 * sizeof(struct pwrfip_ba_request));

        ba_req[0].type = PWRFIP_BA_ID_DAT;
        ba_req[0].id = 0x0500;
        ba_req[0].id |= sess->ping_addr;
        ba_req[1].type = PWRFIP_BA_ID_DAT;
        ba_req[1].id = 0x0600;
        ba_req[1].id |= sess->ping_addr;
        ba_wind_cfg[0].type = PWRFIP_BA_WIND_PER;
        ba_wind_cfg[0].per.req_cnt = 2;
        ba_wind_cfg[0].per.req_list = &ba_req[0];
        ba_wind_cfg[1].type = PWRFIP_BA_WIND_WAIT;
        ba_wind_cfg[1].wait.end_ustime = sess->ba_cycle_ustime;
        ba_wind_cfg[1].wait.is_silent = 1;
        mcycle_cfg.wind_cnt = 2;
        mcycle_cfg.wind_list = &ba_wind_cfg[0];
        mcycle = pwrfip_ba_mcycle_create(sess->node.n, &mcycle_cfg);
        if (!mcycle) {
            _print(src, err, "macrocycle creation failed: %s\n",
                pwrfip_strerror(errno));
            goto fail1;
        }
        sess->node.mc_list[sess->node.mc_cnt] = mcycle;
        sess->node.mc_cnt++;
    }

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

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

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

int tst_session_stop(struct tst_session* sess)
{
    msg_src src = (sess->station_ismstr) ? ping : pong;

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

    /* print session stats */
    _print(src, info, "** session stats:\n");
    _print(src, info,
        "  total events: %"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(src, info,
            "    - occurrence period  (us): min=%"PRIu64", max=%"PRIu64"\n",
            sess->stats.min_sync_cb_last,
            sess->stats.max_sync_cb_last);
        _print(src, 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(src, info, "  var:\n");
        _print(src, info, "    - write: ok=%"PRIu64" [warn=%"PRIu64"]\n",
            sess->stats.w_var_ok, sess->stats.w_var_warn);
        _print(src, info, "    - read : ok=%"PRIu64" [warn=%"PRIu64"]\n",
            sess->stats.r_var_ok, sess->stats.r_var_warn);
        _print(src, info, "    - ping/\n");
        _print(src, info, "      pong : ok=%"PRIu64" [nok=%"PRIu64"]\n",
            sess->stats.pp_ok, sess->stats.pp_nok);
        _print(src, info, "      diff : min=%"PRIu64", max=%"PRIu64"\n",
            sess->stats.min_pp_diff, sess->stats.max_pp_diff);
    }

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

int tst_node_report(struct tst_session* sess)
{
    msg_src src = (sess->station_ismstr) ? ping : pong;
    uint16_t fd_status;
    struct pwrfip_node_report report;

    if (pwrfip_node_report_get(sess->node.n, &report)) {
        _print(src, 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(src, info, "** local report:\n");
    _print(src, info, "  node_state    : %d (%s)\n",
        report.node_status.state, pwrfip_node_state_str[report.node_status.state]);
    _print(src, info, "  node_op       : %d (%s)\n",
        report.node_status.op, pwrfip_node_op_str[report.node_status.op]);
    _print(src, info, "  ba_state      : %d (%s)\n",
        report.ba_status.state, pwrfip_ba_state_str[report.ba_status.state]);
    _print(src, info, "  ba_window     : %d (%s)\n",
        report.ba_status.window, pwrfip_ba_wind_str[report.ba_status.window]);
    _print(src, info, "  medium_state  : 0x%04x\n", fd_status);
    _print(src, info, "    [channel 1]\n");
    _print(src, info, "      enable    : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH1_VALID) ? "yes": "no");
    _print(src, info, "      tx_err    : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH1_TX_ERROR) ? "yes": "no");
    _print(src, info, "      watchdog  : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH1_WATCHDOG) ? "yes": "no");
    _print(src, info, "    [channel 2]\n");
    _print(src, info, "      enable    : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH2_VALID) ? "yes": "no");
    _print(src, info, "      tx_err    : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH2_TX_ERROR) ? "yes": "no");
    _print(src, info, "      watchdog  : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH2_WATCHDOG) ? "yes": "no");
    _print(src, info, "  tx_err        :\n");
    _print(src, info, "    ok          : %d\n", report.tx_err.ok);
    _print(src, info, "    collision   : %d\n", report.tx_err.collision);
    _print(src, info, "    consistency : %d\n", report.tx_err.consistency);
    _print(src, info, "    not_fresh   : %d\n", report.tx_err.not_fresh);
    _print(src, info, "  rx_err        :\n");
    _print(src, info, "    ok          : %d\n", report.rx_err.ok);
    _print(src, info, "    pre_mis     : %d\n", report.rx_err.pre_mis);
    _print(src, info, "    fsd_mis     : %d\n", report.rx_err.fsd_mis);
    _print(src, info, "    fsd_unk     : %d\n", report.rx_err.fsd_unk);
    _print(src, info, "    fed_mis     : %d\n", report.rx_err.fed_mis);
    _print(src, info, "    crc_bad     : %d\n", report.rx_err.crc_bad);
    _print(src, info, "    pdu_bad     : %d\n", report.rx_err.pdu_bad);
    _print(src, info, "    len_bad     : %d\n", report.rx_err.len_bad);
    _print(src, info, "    not_fresh   : %d\n", report.rx_err.not_fresh);
    _print(src, info, "    not_prompt  : %d\n", report.rx_err.not_prompt);
    return 0;
}

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_ismstr) ? ping : pong;
    char *ba_state_str;
    switch (event->code) {
    case PWRFIP_EVT_BA_ACTIVITY: 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_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_ismstr) ? ping : pong;
    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_ismstr) ? ping : pong;
    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_sync_rw_op_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_ismstr) ? ping : pong;
    struct pwrfip_var *c_var = sess->node.cons_varlist[0];
    struct pwrfip_var *p_var = sess->node.prod_varlist[0];
    // unsigned long duration_us = 0;
    unsigned long last_us = 0;
    /* two initialization cycles before master node reports warns */
    static int mcycle_init_cnt = 4;
    static uint64_t w_counter = 0;
    static uint64_t r_counter = 0;
#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 || mcycle_init_cnt) ? 0 : 1;

    _gettime(&start_treatment);

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

            if (sess->station_ismstr) {
                /* PING */
                memcpy(&r_counter, c_var->buffer, sizeof(r_counter));
                if (mcycle_init_cnt == 0) {
                    /* not an initial condition */
                    if ((r_counter + 1) == w_counter)
                        sess->stats.pp_ok++;
                    else {
                        int64_t diff = w_counter - r_counter;
                        uint64_t abs_diff = (uint64_t) ((diff < 0) ? (-1 * diff) : diff);
                        _print(src, warn,
                            "ping/pong anomaly: r=%"PRIu64
                            " vs w=%"PRIu64
                            " [diff=%"PRIi64"]\n",
                            r_counter, w_counter, diff);
                        update_min_max(abs_diff, &sess->stats.min_pp_diff,
                            &sess->stats.max_pp_diff);
                        sess->stats.pp_nok++;
                    }
                }
                /* prepare p_var */
                w_counter++;
                if (p_var->bsz < sizeof(w_counter))
                    memcpy(p_var->buffer, &w_counter, p_var->bsz);
                else
                    memcpy(p_var->buffer, &w_counter, sizeof(w_counter));

            } else {
                /* PONG */
                /* prepare p_var */
                memcpy(p_var->buffer, c_var->buffer, c_var->bsz);
            }
        }
    }

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

    _gettime(&stop_treatment);
    // duration_us = get_elapsed_us(stop_treatment, start_treatment);
    last_us = (mcycle_init_cnt) ? 0 : get_elapsed_us(start_treatment, last_treatment);
    mcycle_init_cnt = (mcycle_init_cnt) ? (mcycle_init_cnt-1) : 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);

    // _print(src, evt, "synchro callback [duration=%ldus - last=%ldus ago]\n",
    //    duration_us, last_us);
}

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_ismstr) ? ping : pong;
    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);
}
