
/*
 * Copyright (C) Jan 2006 Mellanox Technologies Ltd. All rights reserved.
 * Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "output_fmts.h"
#include "img_version.h"
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <sstream>
#include <iostream>
#include "err_msgs.h"
#include <iomanip>

string int2str(int n)
{
    stringstream num;
    string str;

    num << n;
    str = num.str();
    return str;
}

string hex2str(int n, int w = 1)
{
    stringstream num;
    string str;

    num << setfill('0') << setw(w) << hex << n;
    str = num.str();
    return str;
}

void freeNode(xmlNodePtr* node)
{
    if (node && *node)
    {
        xmlUnlinkNode(*node); // Unlink node from the document
        xmlFreeNode(*node);   // Free the node
        *node = NULL;         // Set pointer to NULL to avoid double-free
    }
}

int OutputFmts::createInventoryXML(vector<MlnxDev*>& devs,
                                   PsidLookupDB& psidLookupDB,
                                   int result,
                                   string msg,
                                   string& buffer,
                                   int buf_is_file)
{
    int res = 0;
#ifdef USE_XML
    xmlDocPtr doc = NULL;
    xmlNodePtr root_node = NULL, devices_node = NULL, node = NULL; // device_node = NULL, fw_node = NULL, ;
    doc = xmlNewDoc(BAD_CAST "1.0");

    root_node = xmlNewNode(NULL, BAD_CAST "SVMInventory");
    xmlNewProp(root_node, BAD_CAST "lang", BAD_CAST "en");
    xmlDocSetRootElement(doc, root_node);

    for (unsigned i = 0; i < devs.size(); i++)
    {
        devices_node = xmlNewChild(root_node, NULL, BAD_CAST "Device", NULL);
        xmlNewProp(devices_node, BAD_CAST "vendorID", BAD_CAST hex2str(devs[i]->getDevInfo()->pci.vend_id).c_str());
        xmlNewProp(devices_node, BAD_CAST "deviceID", BAD_CAST hex2str(devs[i]->getDevInfo()->pci.dev_id).c_str());
        xmlNewProp(devices_node, BAD_CAST "subDeviceID",
                   BAD_CAST hex2str(devs[i]->getDevInfo()->pci.subsys_id, 4).c_str());
        xmlNewProp(devices_node, BAD_CAST "subVendorID",
                   BAD_CAST hex2str(devs[i]->getDevInfo()->pci.subsys_vend_id).c_str());
        xmlNewProp(devices_node, BAD_CAST "domain", BAD_CAST hex2str(devs[i]->getDevInfo()->pci.domain).c_str());
        xmlNewProp(devices_node, BAD_CAST "bus", BAD_CAST hex2str(devs[i]->getDevInfo()->pci.bus).c_str());
        xmlNewProp(devices_node, BAD_CAST "device", BAD_CAST hex2str(devs[i]->getDevInfo()->pci.dev).c_str());
        xmlNewProp(devices_node, BAD_CAST "function", BAD_CAST hex2str(devs[i]->getDevInfo()->pci.func).c_str());
        if (!devs[i]->isQuerySuccess())
        {
            continue;
        }
        xmlNewProp(devices_node, BAD_CAST "display",
                   BAD_CAST psidLookupDB.getProdDesc(devs[i]->getBoardTypeId()).c_str());
        node = xmlNewChild(devices_node, NULL, BAD_CAST "Application", NULL);
        xmlNewProp(node, BAD_CAST "componentType", BAD_CAST "FRMW");
        xmlNewProp(node, BAD_CAST "version", BAD_CAST devs[i]->getFWVersion(false).c_str());
        xmlNewProp(node, BAD_CAST "display", BAD_CAST psidLookupDB.getProdDesc(devs[i]->getBoardTypeId()).c_str());
    }

    if (result)
    {
        node = xmlNewChild(root_node, NULL, BAD_CAST "SPStatus", NULL);
        xmlNewProp(node, BAD_CAST "result", BAD_CAST "false");
        node = xmlNewChild(node, NULL, BAD_CAST "Message", BAD_CAST msg.c_str());
        xmlNewProp(node, BAD_CAST "id", BAD_CAST int2str(result).c_str());
    }

    if (buf_is_file)
    {
        int rc = xmlSaveFormatFileEnc(buffer.c_str(), doc, "UTF-8", 1);
        if (rc == -1)
        {
            res = -1;
        }
    }
    else
    {
        xmlChar* doc_txt_ptr = NULL;
        int doc_txt_len = 0;

        xmlDocDumpFormatMemoryEnc(doc, &doc_txt_ptr, &doc_txt_len, "UTF-8", 1);
        if (doc_txt_len)
        {
            buffer = (char*)doc_txt_ptr;
            // xmlFree(doc_txt_ptr); //TODO: This is a leak, temporarily only until windows crash is resolved!!
        }
    }
    freeNode(&root_node);
    xmlFreeDoc(doc);
    xmlCleanupParser();
#endif
    return res;
}

int OutputFmts::createBurnXML(vector<MlnxDev*>& devs,
                              map<string, PsidQueryItem>& psidUpdateInfo,
                              PsidLookupDB& psidLookupDB,
                              string& buffer,
                              int buf_is_file,
                              int compareFFV)
{
    int res = 0;
#ifdef USE_XML
    int boot_required = 0;
    xmlDocPtr doc = NULL;
    xmlNodePtr root_node = NULL, devices_node = NULL, node = NULL, app_node = NULL;
    doc = xmlNewDoc(BAD_CAST "1.0");

    root_node = xmlNewNode(NULL, BAD_CAST "SVMExecution");
    xmlNewProp(root_node, BAD_CAST "lang", BAD_CAST "en");
    xmlDocSetRootElement(doc, root_node);
    for (unsigned i = 0; i < devs.size(); i++)
    {
        devices_node = xmlNewChild(root_node, NULL, BAD_CAST "Device", NULL);
        xmlNewProp(devices_node, BAD_CAST "vendorID", BAD_CAST hex2str(devs[i]->getDevInfo()->pci.vend_id).c_str());
        xmlNewProp(devices_node, BAD_CAST "deviceID", BAD_CAST hex2str(devs[i]->getDevInfo()->pci.dev_id).c_str());
        xmlNewProp(devices_node, BAD_CAST "subDeviceID",
                   BAD_CAST hex2str(devs[i]->getDevInfo()->pci.subsys_id, 4).c_str());
        xmlNewProp(devices_node, BAD_CAST "subVendorID",
                   BAD_CAST hex2str(devs[i]->getDevInfo()->pci.subsys_vend_id).c_str());
        xmlNewProp(devices_node, BAD_CAST "bus", BAD_CAST hex2str(devs[i]->getDevInfo()->pci.bus).c_str());
        xmlNewProp(devices_node, BAD_CAST "device", BAD_CAST hex2str(devs[i]->getDevInfo()->pci.dev).c_str());
        xmlNewProp(devices_node, BAD_CAST "function", BAD_CAST hex2str(devs[i]->getDevInfo()->pci.func).c_str());
        xmlNewProp(devices_node, BAD_CAST "display",
                   BAD_CAST psidLookupDB.getProdDesc(devs[i]->getBoardTypeId()).c_str());
        if (!devs[i]->isQuerySuccess())
        {
            node = xmlNewChild(devices_node, NULL, BAD_CAST "SPStatus", NULL);
            xmlNewProp(node, BAD_CAST "result", BAD_CAST "false");
            node = xmlNewChild(node, NULL, BAD_CAST "Message", BAD_CAST "Failed to query the device.");
            xmlNewProp(node, BAD_CAST "id", BAD_CAST int2str(ERR_CODE_QUERY_FAILED).c_str());
            continue;
        }
        app_node = xmlNewChild(devices_node, NULL, BAD_CAST "Application", NULL);
        xmlNewProp(app_node, BAD_CAST "componentType", BAD_CAST "FRMW");
        xmlNewProp(app_node, BAD_CAST "version", BAD_CAST devs[i]->getFWVersion(false).c_str());
        xmlNewProp(app_node, BAD_CAST "display", BAD_CAST psidLookupDB.getProdDesc(devs[i]->getBoardTypeId()).c_str());

        if (psidUpdateInfo.find(devs[i]->getPsid()) != psidUpdateInfo.end())
        {
            if (psidUpdateInfo[devs[i]->getPsid()].found)
            {
                node = xmlNewChild(app_node, NULL, BAD_CAST "Package", NULL);
                ImgVersion* fwImgVer = (ImgVersion*)psidUpdateInfo[devs[i]->getPsid()].findImageVersion("FW");
                if (fwImgVer != NULL)
                {
                    xmlNewProp(node, BAD_CAST "version",
                               BAD_CAST fwImgVer->getPrintableVersion(compareFFV, false).c_str());
                }
            }
        }
        node = xmlNewChild(app_node, NULL, BAD_CAST "SPStatus", NULL);

        int burn_res = ERR_CODE_PROG_FAILED;
        string msg = "Update failure.";
        if (devs[i]->isBurnSuccess() == 0)
        {
            msg += " ";
            msg += devs[i]->getLastErrMsg();
        }
        else if (devs[i]->isBurnSuccess() == 1)
        {
            boot_required = 1;
            burn_res = MLX_FWM_SUCCESS;
            msg = "Update success.";
        }
        else
        { // == -1
            if (psidUpdateInfo.find(devs[i]->getPsid()) != psidUpdateInfo.end())
            {
                if (!psidUpdateInfo[devs[i]->getPsid()].found)
                {
                    msg += " No relevant image file was found for this device";
                }
                else if (psidUpdateInfo[devs[i]->getPsid()].found > 1)
                {
                    msg += " Multiple image sources found for this device";
                }
                else
                {
                    ImgVersion* fwImgVer = (ImgVersion*)psidUpdateInfo[devs[i]->getPsid()].findImageVersion("FW");
                    if (fwImgVer != NULL)
                    {
                        if ((devs[i]->compareFWVer(*fwImgVer) <= 0) && !_force_mode)
                        {
                            burn_res = MLX_FWM_SUCCESS;
                            msg = "Update success. No update required.";
                        }
                    }
                }
            }
            else
            {
                msg = "Update failure. No relevant image file was found for this device";
            }
        }

        xmlNewProp(node, BAD_CAST "result", BAD_CAST(burn_res ? "false" : "true"));
        node = xmlNewChild(node, NULL, BAD_CAST "Message", BAD_CAST msg.c_str());
        xmlNewProp(node, BAD_CAST "id", BAD_CAST int2str(burn_res).c_str());
    }
    node = xmlNewChild(root_node, NULL, BAD_CAST "RebootRequired", BAD_CAST int2str(boot_required).c_str());

    if (buf_is_file)
    {
        int rc = xmlSaveFormatFileEnc(buffer.c_str(), doc, "UTF-8", 1);
        if (rc == -1)
        {
            res = -1;
        }
    }
    else
    {
        xmlChar* doc_txt_ptr = NULL;
        int doc_txt_len = 0;

        xmlDocDumpFormatMemoryEnc(doc, &doc_txt_ptr, &doc_txt_len, "UTF-8", 1);
        if (doc_txt_len)
        {
            buffer = (char*)doc_txt_ptr;
            // xmlFree(doc_txt_ptr); //TODO: This is a leak, temporarily only until windows crash is resolved!!
        }
    }
    (void)node;
    xmlFreeDoc(doc);
    xmlCleanupParser();
#endif
    return res;
}

int OutputFmts::createFailXML(int result, int update_query_, string& buffer, int buf_is_file)
{
    int res = 0;
#ifdef USE_XML
    xmlDocPtr doc = NULL;
    xmlNodePtr root_node = NULL, node = NULL;
    doc = xmlNewDoc(BAD_CAST "1.0");

    if (update_query_ == 0)
    {
        root_node = xmlNewNode(NULL, BAD_CAST "SVMInventory");
    }
    else
    {
        root_node = xmlNewNode(NULL, BAD_CAST "SVMExecution");
    }
    xmlNewProp(root_node, BAD_CAST "lang", BAD_CAST "en");
    xmlDocSetRootElement(doc, root_node);

    node = xmlNewChild(root_node, NULL, BAD_CAST "SPStatus", NULL);
    xmlNewProp(node, BAD_CAST "result", BAD_CAST "false");
    if (update_query_ == 1)
    {
        xmlNewProp(node, BAD_CAST "code", BAD_CAST int2str(result).c_str());
    }
    node = xmlNewChild(node, NULL, BAD_CAST "Message", BAD_CAST getErrStr(result).c_str());
    xmlNewProp(node, BAD_CAST "id", BAD_CAST int2str(result).c_str());

    if (update_query_ == 1)
    {
        node = xmlNewChild(root_node, NULL, BAD_CAST "RebootRequired", BAD_CAST "0");
    }

    if (buf_is_file)
    {
        int rc = xmlSaveFormatFileEnc(buffer.c_str(), doc, "UTF-8", 1);
        if (rc == -1)
        {
            res = -1;
        }
    }
    else
    {
        xmlChar* doc_txt_ptr = NULL;
        int doc_txt_len = 0;

        xmlDocDumpFormatMemoryEnc(doc, &doc_txt_ptr, &doc_txt_len, "UTF-8", 1);
        if (doc_txt_len)
        {
            buffer = (char*)doc_txt_ptr;
            // xmlFree(doc_txt_ptr); //TODO: This is a leak, temporarily only until windows crash is resolved!!
        }
    }
    (void)node;
    xmlFreeDoc(doc);
    xmlCleanupParser();
#endif
    return res;
}

void OutputFmts::setForceModeParam(bool force_mode)
{
    _force_mode = force_mode;
}
