/*
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under
 * the OAI Public License, Version 1.1  (the "License"); you may not use this file
 * except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.openairinterface.org/?page_id=698
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *-------------------------------------------------------------------------------
 * For more information about the OpenAirInterface (OAI) Software Alliance:
 *      contact@openairinterface.org
 */

/*
                                play_scenario_s1ap_compare_ie.c
                                -------------------
  AUTHOR  : Lionel GAUTHIER
  COMPANY : EURECOM
  EMAIL   : Lionel.Gauthier@eurecom.fr
 */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <unistd.h>
#include <crypt.h>
#include <errno.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include "tree.h"
#include "queue.h"


#include "intertask_interface.h"
#include "platform_types.h"
#include "assertions.h"
#include "conversions.h"
#include "s1ap_common.h"
#include "play_scenario_s1ap_eNB_defs.h"
#include "play_scenario.h"
#include "msc.h"
//------------------------------------------------------------------------------
extern et_scenario_t  *g_scenario;
extern uint32_t        g_constraints;
//------------------------------------------------------------------------------

//asn_comp_rval_t * et_s1ap_ies_is_matching(const S1AP_PDU_PR present, s1ap_message * const m1, s1ap_message * const m2, const uint32_t constraints)
//{
//}

void update_xpath_node_mme_ue_s1ap_id(et_s1ap_t * const s1ap, xmlNode *node, const S1ap_MME_UE_S1AP_ID_t new_id)
{
  xmlNode       *cur_node = NULL;
  xmlAttrPtr     attr     = NULL;
  xmlChar       *xml_char = NULL;
  int            size     = 0;
  int            pos      = 0;
  int            go_deeper_in_tree = 1;
  //S1AP_INFO("%s() mme_ue_s1ap_id %u\n", __FUNCTION__, new_id);

  // modify
  for (cur_node = (xmlNode *)node; cur_node; cur_node = cur_node->next) {
    go_deeper_in_tree = 1;
    if ((!xmlStrcmp(cur_node->name, (const xmlChar *)"field"))) {
      // do not get hidden fields
      xml_char = xmlGetProp((xmlNode *)cur_node, (const xmlChar *)"hide");
      if (NULL != xml_char) {
        if ((!xmlStrcmp(xml_char, (const xmlChar *)"yes"))) {
          go_deeper_in_tree = 0;
        }
        xmlFree(xml_char);
      }
      if (0 < go_deeper_in_tree) {
        // first get size
        xml_char = xmlGetProp((xmlNode *)cur_node, (const xmlChar *)"pos");
        if (NULL != xml_char) {
          pos = strtoul((const char *)xml_char, NULL, 0);
          pos -= s1ap->xml_stream_pos_offset;
          AssertFatal(pos >= 0, "Bad pos %d xml_stream_pos_offset %d", pos, s1ap->xml_stream_pos_offset);
          xmlFree(xml_char);
          xml_char = xmlGetProp((xmlNode *)cur_node, (const xmlChar *)"size");
          if (NULL != xml_char) {
            const xmlChar value_d[32];
            const xmlChar value_h[20];
            const xmlChar showname[64];
            int           ret     = 0;
            int           pos2    = 0;
            unsigned long int uli = 0;
            char          hex[3]  = {0,0,0};
            char         *end_ptr = NULL;

            size = strtoul((const char *)xml_char, NULL, 0);
            xmlFree(xml_char);
            // second: try to set value (always hex)
            ret = snprintf((char *)value_d, 32, "%ld", new_id);
            AssertFatal((ret > 0) && (ret < 32), "Could not convert int to dec str");
            ret = snprintf((char *)value_h, 20, "C0%08X", new_id);
            AssertFatal((ret > 0) && (ret < 20), "Could not convert int to hex str");
            ret = snprintf((char *)showname, 64, "MME-UE-S1AP-ID: %d", new_id);
            AssertFatal((ret > 0) && (ret < 64), "Could not convert int to dec str");

            attr = xmlSetProp((xmlNode *)cur_node, (const xmlChar *)"value", value_h);
            attr = xmlSetProp((xmlNode *)cur_node, (const xmlChar *)"show", value_d);
            attr = xmlSetProp((xmlNode *)cur_node, (const xmlChar *)"showname", showname);
            //TODO update s1ap->binary_stream @pos with new_id_hex, size
            AssertFatal((pos+size) < s1ap->binary_stream_allocated_size, "Rewrite of mme_ue_s1ap_id out of bounds of binary_stream");
            //avoid endianess issues
            do {
              hex[0] = value_h[pos2++];
              hex[1] = value_h[pos2++];
              hex[2] = '\0';
              end_ptr = hex;
              uli = strtoul(hex, &end_ptr, 16);
              AssertFatal((uli != ULONG_MAX) && (end_ptr != NULL) && (*end_ptr == '\0'), "Conversion of hexstring %s failed returned %ld errno %d", hex, uli, errno);
              s1ap->binary_stream[pos++] = (unsigned char)uli;
            } while (pos2 < (2*5));
            // update ASN1
            et_decode_s1ap(s1ap);
            //S1AP_INFO("Updated ASN1 for %s\n", showname);
          }
        }
      }
    }
    if (0 < go_deeper_in_tree) {
      update_xpath_node_mme_ue_s1ap_id(s1ap, cur_node->children, new_id);
    }
  }
}

/**
 * update_xpath_nodes:
 * @nodes:    the nodes set.
 * @value:    the new value for the node(s)
 *
 * Prints the @nodes content to @output.
 * From http://www.xmlsoft.org/examples/#xpath2.c
 */
void update_xpath_nodes_mme_ue_s1ap_id(et_s1ap_t * const s1ap_payload, xmlNodeSetPtr nodes, const S1ap_MME_UE_S1AP_ID_t new_id)
{
  int           size = 0;
  int           i    = 0;
  xmlNode      *s1ap_node = NULL;

  size = (nodes) ? nodes->nodeNr : 0;
  //S1AP_DEBUG("%s() num nodes %u\n", __FUNCTION__, size);

  /*
   * NOTE: the nodes are processed in reverse order, i.e. reverse document
   *       order because xmlNodeSetContent can actually free up descendant
   *       of the node and such nodes may have been selected too ! Handling
   *       in reverse order ensure that descendant are accessed first, before
   *       they get removed. Mixing XPath and modifications on a tree must be
   *       done carefully !
   */
  for(i = size - 1; i >= 0; i--) {
    s1ap_node = nodes->nodeTab[i];
    AssertFatal(NULL != s1ap_node, "One element of resultset of XPATH expression is NULL\n");
    update_xpath_node_mme_ue_s1ap_id(s1ap_payload, s1ap_node, new_id);
    /*
     * All the elements returned by an XPath query are pointers to
     * elements from the tree *except* namespace nodes where the XPath
     * semantic is different from the implementation in libxml2 tree.
     * As a result when a returned node set is freed when
     * xmlXPathFreeObject() is called, that routine must check the
     * element type. But node from the returned set may have been removed
     * by xmlNodeSetContent() resulting in access to freed data.
     * This can be exercised by running valgrind
     * There is 2 ways around it:
     *   - make a copy of the pointers to the nodes from the result set
     *     then call xmlXPathFreeObject() and then modify the nodes
     * or
     *   - remove the reference to the modified nodes from the node set
     *     as they are processed, if they are not namespace nodes.
     */
    if (nodes->nodeTab[i]->type != XML_NAMESPACE_DECL) {
      nodes->nodeTab[i] = NULL;
    }
  }
}
//------------------------------------------------------------------------------
int et_s1ap_update_mme_ue_s1ap_id(et_packet_t * const packet, const S1ap_MME_UE_S1AP_ID_t old_id, const S1ap_MME_UE_S1AP_ID_t new_id)
{
  xmlChar              xpath_expression[ET_XPATH_EXPRESSION_MAX_LENGTH];
  int                  ret       = 0;
  xmlDocPtr            doc       = NULL;
  xmlXPathContextPtr   xpath_ctx = NULL;
  xmlXPathObjectPtr    xpath_obj = NULL;

  //S1AP_DEBUG("%s() packet num %u original frame number %u, mme_ue_s1ap_id %u -> %u\n", __FUNCTION__, packet->packet_number, packet->original_frame_number, old_id, new_id);

  ret = snprintf(xpath_expression, ET_XPATH_EXPRESSION_MAX_LENGTH, "//field[@name=\"s1ap.MME_UE_S1AP_ID\"][@show=\"%u\"]", old_id);
  AssertFatal((ret > 0) && (ret < ET_XPATH_EXPRESSION_MAX_LENGTH), "Could not build XPATH expression err=%d", ret);

  doc = packet->sctp_hdr.u.data_hdr.payload.doc;
  // Create xpath evaluation context
  xpath_ctx = xmlXPathNewContext(doc);
  if(xpath_ctx == NULL) {
      fprintf(stderr,"Error: unable to create new XPath context\n");
      xmlFreeDoc(doc);
      return(-1);
  }

  // Evaluate xpath expression
  xpath_obj = xmlXPathEvalExpression(xpath_expression, xpath_ctx);
  xmlXPathFreeContext(xpath_ctx);
  AssertFatal(xpath_obj != NULL, "Unable to evaluate XPATH expression \"%s\"\n", xpath_expression);

  if(xmlXPathNodeSetIsEmpty(xpath_obj->nodesetval)){
    xmlXPathFreeObject(xpath_obj);
    S1AP_DEBUG("%s() No match \"%s\"packet num %u original frame number %u, mme_ue_s1ap_id %u -> %u\n",
        __FUNCTION__, xpath_expression, packet->packet_number, packet->original_frame_number, old_id, new_id);
    return -1;
  }
  // update selected nodes
  update_xpath_nodes_mme_ue_s1ap_id(&packet->sctp_hdr.u.data_hdr.payload, xpath_obj->nodesetval, new_id);

  // Cleanup of XPath data
  xmlXPathFreeObject(xpath_obj);

  return 0;
}
