/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */

/*
 *  Import and export general routing data using a XML file.
 */

#include <android-base/stringprintf.h>
#include <base/logging.h>
#include <errno.h>
#include <sys/stat.h>

/* NOTE:
 * This has to be included AFTER the android-base includes since
 * android-base/macros.h defines ATTRIBUTE_UNUSED, also used in the
 * tiny XML library.
 */
#include "RouteDataSet.h"

#include "libxml/xmlmemory.h"

using android::base::StringPrintf;

extern std::string nfc_storage_path;
extern bool nfc_debug_enabled;

/*******************************************************************************
**
** Function:        AidBuffer
**
** Description:     Parse a string of hex numbers.  Store result in an array of
**                  bytes.
**                  aid: string of hex numbers.
**
** Returns:         None.
**
*******************************************************************************/
AidBuffer::AidBuffer(std::string& aid) : mBuffer(NULL), mBufferLen(0) {
  unsigned int num = 0;
  const char delimiter = ':';
  std::string::size_type pos1 = 0;
  std::string::size_type pos2 = aid.find_first_of(delimiter);

  // parse the AID string; each hex number is separated by a colon;
  mBuffer = new uint8_t[aid.length()];
  while (true) {
    num = 0;
    if (pos2 == std::string::npos) {
      sscanf(aid.substr(pos1).c_str(), "%x", &num);
      mBuffer[mBufferLen] = (uint8_t)num;
      mBufferLen++;
      break;
    } else {
      sscanf(aid.substr(pos1, pos2 - pos1 + 1).c_str(), "%x", &num);
      mBuffer[mBufferLen] = (uint8_t)num;
      mBufferLen++;
      pos1 = pos2 + 1;
      pos2 = aid.find_first_of(delimiter, pos1);
    }
  }
}

/*******************************************************************************
**
** Function:        ~AidBuffer
**
** Description:     Release all resources.
**
** Returns:         None.
**
*******************************************************************************/
AidBuffer::~AidBuffer() { delete[] mBuffer; }

/*******************************************************************************/
/*******************************************************************************/

const char* RouteDataSet::sConfigFile = "/param/route.xml";

/*******************************************************************************
**
** Function:        ~RouteDataSet
**
** Description:     Release all resources.
**
** Returns:         None.
**
*******************************************************************************/
RouteDataSet::~RouteDataSet() { deleteDatabase(); }

/*******************************************************************************
**
** Function:        initialize
**
** Description:     Initialize resources.
**
** Returns:         True if ok.
**
*******************************************************************************/
bool RouteDataSet::initialize() {
  DLOG_IF(INFO, nfc_debug_enabled)
      << StringPrintf("%s: enter", "RouteDataSet::initialize");
  // check that the libxml2 version in use is compatible
  // with the version the software has been compiled with
  LIBXML_TEST_VERSION
  DLOG_IF(INFO, nfc_debug_enabled)
      << StringPrintf("%s: exit; return=true", "RouteDataSet::initialize");
  return true;
}

/*******************************************************************************
**
** Function:        deleteDatabase
**
** Description:     Delete all routes stored in all databases.
**
** Returns:         None.
**
*******************************************************************************/
void RouteDataSet::deleteDatabase() {
  DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf(
      "%s: default db size=%zu; sec elem db size=%zu",
      "RouteDataSet::deleteDatabase", mDefaultRouteDatabase.size(),
      mSecElemRouteDatabase.size());
  Database::iterator it;

  for (it = mDefaultRouteDatabase.begin(); it != mDefaultRouteDatabase.end();
       it++)
    delete (*it);
  mDefaultRouteDatabase.clear();

  for (it = mSecElemRouteDatabase.begin(); it != mSecElemRouteDatabase.end();
       it++)
    delete (*it);
  mSecElemRouteDatabase.clear();
}

/*******************************************************************************
**
** Function:        import
**
** Description:     Import data from an XML file.  Fill the databases.
**
** Returns:         True if ok.
**
*******************************************************************************/
bool RouteDataSet::import() {
  static const char fn[] = "RouteDataSet::import";
  DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s: enter", fn);
  bool retval = false;
  xmlDocPtr doc;
  xmlNodePtr node1;
  std::string strFilename(nfc_storage_path);
  strFilename += sConfigFile;

  deleteDatabase();

  doc = xmlParseFile(strFilename.c_str());
  if (doc == NULL) {
    DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s: fail parse", fn);
    goto TheEnd;
  }

  node1 = xmlDocGetRootElement(doc);
  if (node1 == NULL) {
    LOG(ERROR) << StringPrintf("%s: fail root element", fn);
    goto TheEnd;
  }
  DLOG_IF(INFO, nfc_debug_enabled)
      << StringPrintf("%s: root=%s", fn, node1->name);

  node1 = node1->xmlChildrenNode;
  while (node1)  // loop through all elements in <Routes ...
  {
    if (xmlStrcmp(node1->name, (const xmlChar*)"Route") == 0) {
      xmlChar* value = xmlGetProp(node1, (const xmlChar*)"Type");
      if (value &&
          (xmlStrcmp(value, (const xmlChar*)"SecElemSelectedRoutes") == 0)) {
        DLOG_IF(INFO, nfc_debug_enabled)
            << StringPrintf("%s: found SecElemSelectedRoutes", fn);
        xmlNodePtr node2 = node1->xmlChildrenNode;
        while (node2)  // loop all elements in <Route
                       // Type="SecElemSelectedRoutes" ...
        {
          if (xmlStrcmp(node2->name, (const xmlChar*)"Proto") == 0)
            importProtocolRoute(node2, mSecElemRouteDatabase);
          else if (xmlStrcmp(node2->name, (const xmlChar*)"Tech") == 0)
            importTechnologyRoute(node2, mSecElemRouteDatabase);
          node2 = node2->next;
        }  // loop all elements in <Route Type="SecElemSelectedRoutes" ...
      } else if (value &&
                 (xmlStrcmp(value, (const xmlChar*)"DefaultRoutes") == 0)) {
        DLOG_IF(INFO, nfc_debug_enabled)
            << StringPrintf("%s: found DefaultRoutes", fn);
        xmlNodePtr node2 = node1->xmlChildrenNode;
        while (node2)  // loop all elements in <Route Type="DefaultRoutes" ...
        {
          if (xmlStrcmp(node2->name, (const xmlChar*)"Proto") == 0)
            importProtocolRoute(node2, mDefaultRouteDatabase);
          else if (xmlStrcmp(node2->name, (const xmlChar*)"Tech") == 0)
            importTechnologyRoute(node2, mDefaultRouteDatabase);
          node2 = node2->next;
        }  // loop all elements in <Route Type="DefaultRoutes" ...
      }
      if (value) xmlFree(value);
    }  // check <Route ...
    node1 = node1->next;
  }  // loop through all elements in <Routes ...
  retval = true;

TheEnd:
  xmlFreeDoc(doc);
  xmlCleanupParser();
  DLOG_IF(INFO, nfc_debug_enabled)
      << StringPrintf("%s: exit; return=%u", fn, retval);
  return retval;
}

/*******************************************************************************
**
** Function:        saveToFile
**
** Description:     Save XML data from a string into a file.
**                  routesXml: XML that represents routes.
**
** Returns:         True if ok.
**
*******************************************************************************/
bool RouteDataSet::saveToFile(const char* routesXml) {
  static const char fn[] = "RouteDataSet::saveToFile";
  FILE* fh = NULL;
  size_t actualWritten = 0;
  bool retval = false;
  std::string filename(nfc_storage_path);
  int stat = 0;

  filename.append(sConfigFile);
  fh = fopen(filename.c_str(), "w");
  if (fh == NULL) {
    LOG(ERROR) << StringPrintf("%s: fail to open file", fn);
    return false;
  }

  actualWritten = fwrite(routesXml, sizeof(char), strlen(routesXml), fh);
  retval = actualWritten == strlen(routesXml);
  fclose(fh);
  DLOG_IF(INFO, nfc_debug_enabled)
      << StringPrintf("%s: wrote %zu bytes", fn, actualWritten);
  if (retval == false) LOG(ERROR) << StringPrintf("%s: error during write", fn);

  // set file permission to
  // owner read, write; group read; other read
  stat = chmod(filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  if (stat == -1) LOG(ERROR) << StringPrintf("%s: error during chmod", fn);
  return retval;
}

/*******************************************************************************
**
** Function:        loadFromFile
**
** Description:     Load XML data from file into a string.
**                  routesXml: string to receive XML data.
**
** Returns:         True if ok.
**
*******************************************************************************/
bool RouteDataSet::loadFromFile(std::string& routesXml) {
  FILE* fh = NULL;
  size_t actual = 0;
  char buffer[1024];
  std::string filename(nfc_storage_path);

  filename.append(sConfigFile);
  fh = fopen(filename.c_str(), "r");
  if (fh == NULL) {
    DLOG_IF(INFO, nfc_debug_enabled)
        << StringPrintf("%s: fail to open file", "RouteDataSet::loadFromFile");
    return false;
  }

  while (true) {
    actual = fread(buffer, sizeof(char), sizeof(buffer), fh);
    if (actual == 0) break;
    routesXml.append(buffer, actual);
  }
  fclose(fh);
  DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf(
      "%s: read %zu bytes", "RouteDataSet::loadFromFile", routesXml.length());
  return true;
}

/*******************************************************************************
**
** Function:        importProtocolRoute
**
** Description:     Parse data for protocol routes.
**                  element: XML node for one protocol route.
**                  database: store data in this database.
**
** Returns:         None.
**
*******************************************************************************/
void RouteDataSet::importProtocolRoute(xmlNodePtr& element,
                                       Database& database) {
  const xmlChar* id = (const xmlChar*)"Id";
  const xmlChar* secElem = (const xmlChar*)"SecElem";
  const xmlChar* trueString = (const xmlChar*)"true";
  const xmlChar* switchOn = (const xmlChar*)"SwitchOn";
  const xmlChar* switchOff = (const xmlChar*)"SwitchOff";
  const xmlChar* batteryOff = (const xmlChar*)"BatteryOff";
  RouteDataForProtocol* data = new RouteDataForProtocol;
  xmlChar* value = NULL;

  DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf(
      "%s: element=%s", "RouteDataSet::importProtocolRoute", element->name);
  value = xmlGetProp(element, id);
  if (value) {
    if (xmlStrcmp(value, (const xmlChar*)"T1T") == 0)
      data->mProtocol = NFA_PROTOCOL_MASK_T1T;
    else if (xmlStrcmp(value, (const xmlChar*)"T2T") == 0)
      data->mProtocol = NFA_PROTOCOL_MASK_T2T;
    else if (xmlStrcmp(value, (const xmlChar*)"T3T") == 0)
      data->mProtocol = NFA_PROTOCOL_MASK_T3T;
    else if (xmlStrcmp(value, (const xmlChar*)"IsoDep") == 0)
      data->mProtocol = NFA_PROTOCOL_MASK_ISO_DEP;
    xmlFree(value);
    DLOG_IF(INFO, nfc_debug_enabled)
        << StringPrintf("%s: %s=0x%X", "RouteDataSet::importProtocolRoute", id,
                        data->mProtocol);
  }

  value = xmlGetProp(element, secElem);
  if (value) {
    data->mNfaEeHandle = strtol((char*)value, NULL, 16);
    xmlFree(value);
    data->mNfaEeHandle = data->mNfaEeHandle | NFA_HANDLE_GROUP_EE;
    DLOG_IF(INFO, nfc_debug_enabled)
        << StringPrintf("%s: %s=0x%X", "RouteDataSet::importProtocolRoute",
                        secElem, data->mNfaEeHandle);
  }

  value = xmlGetProp(element, switchOn);
  if (value) {
    data->mSwitchOn = (xmlStrcmp(value, trueString) == 0);
    xmlFree(value);
  }

  value = xmlGetProp(element, switchOff);
  if (value) {
    data->mSwitchOff = (xmlStrcmp(value, trueString) == 0);
    xmlFree(value);
  }

  value = xmlGetProp(element, batteryOff);
  if (value) {
    data->mBatteryOff = (xmlStrcmp(value, trueString) == 0);
    xmlFree(value);
  }
  database.push_back(data);
}

/*******************************************************************************
**
** Function:        importTechnologyRoute
**
** Description:     Parse data for technology routes.
**                  element: XML node for one technology route.
**                  database: store data in this database.
**
** Returns:         None.
**
*******************************************************************************/
void RouteDataSet::importTechnologyRoute(xmlNodePtr& element,
                                         Database& database) {
  const xmlChar* id = (const xmlChar*)"Id";
  const xmlChar* secElem = (const xmlChar*)"SecElem";
  const xmlChar* trueString = (const xmlChar*)"true";
  const xmlChar* switchOn = (const xmlChar*)"SwitchOn";
  const xmlChar* switchOff = (const xmlChar*)"SwitchOff";
  const xmlChar* batteryOff = (const xmlChar*)"BatteryOff";
  RouteDataForTechnology* data = new RouteDataForTechnology;
  xmlChar* value = NULL;

  DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf(
      "%s: element=%s", "RouteDataSet::importTechnologyRoute", element->name);
  value = xmlGetProp(element, id);
  if (value) {
    if (xmlStrcmp(value, (const xmlChar*)"NfcA") == 0)
      data->mTechnology = NFA_TECHNOLOGY_MASK_A;
    else if (xmlStrcmp(value, (const xmlChar*)"NfcB") == 0)
      data->mTechnology = NFA_TECHNOLOGY_MASK_B;
    else if (xmlStrcmp(value, (const xmlChar*)"NfcF") == 0)
      data->mTechnology = NFA_TECHNOLOGY_MASK_F;
    xmlFree(value);
    DLOG_IF(INFO, nfc_debug_enabled)
        << StringPrintf("%s: %s=0x%X", "RouteDataSet::importTechnologyRoute",
                        id, data->mTechnology);
  }

  value = xmlGetProp(element, secElem);
  if (value) {
    data->mNfaEeHandle = strtol((char*)value, NULL, 16);
    xmlFree(value);
    data->mNfaEeHandle = data->mNfaEeHandle | NFA_HANDLE_GROUP_EE;
    DLOG_IF(INFO, nfc_debug_enabled)
        << StringPrintf("%s: %s=0x%X", "RouteDataSet::importTechnologyRoute",
                        secElem, data->mNfaEeHandle);
  }

  value = xmlGetProp(element, switchOn);
  if (value) {
    data->mSwitchOn = (xmlStrcmp(value, trueString) == 0);
    xmlFree(value);
  }

  value = xmlGetProp(element, switchOff);
  if (value) {
    data->mSwitchOff = (xmlStrcmp(value, trueString) == 0);
    xmlFree(value);
  }

  value = xmlGetProp(element, batteryOff);
  if (value) {
    data->mBatteryOff = (xmlStrcmp(value, trueString) == 0);
    xmlFree(value);
  }
  database.push_back(data);
}

/*******************************************************************************
**
** Function:        deleteFile
**
** Description:     Delete route data XML file.
**
** Returns:         True if ok.
**
*******************************************************************************/
bool RouteDataSet::deleteFile() {
  static const char fn[] = "RouteDataSet::deleteFile";
  std::string filename(nfc_storage_path);
  filename.append(sConfigFile);
  int stat = remove(filename.c_str());
  DLOG_IF(INFO, nfc_debug_enabled)
      << StringPrintf("%s: exit %u", fn, stat == 0);
  return stat == 0;
}

/*******************************************************************************
**
** Function:        getDatabase
**
** Description:     Obtain a database of routing data.
**                  selection: which database.
**
** Returns:         Pointer to database.
**
*******************************************************************************/
RouteDataSet::Database* RouteDataSet::getDatabase(DatabaseSelection selection) {
  switch (selection) {
    case DefaultRouteDatabase:
      return &mDefaultRouteDatabase;
    case SecElemRouteDatabase:
      return &mSecElemRouteDatabase;
  }
  return NULL;
}

/*******************************************************************************
**
** Function:        printDiagnostic
**
** Description:     Print some diagnostic output.
**
** Returns:         None.
**
*******************************************************************************/
void RouteDataSet::printDiagnostic() {
  static const char fn[] = "RouteDataSet::printDiagnostic";
  Database* db = getDatabase(DefaultRouteDatabase);

  DLOG_IF(INFO, nfc_debug_enabled)
      << StringPrintf("%s: default route database", fn);
  for (Database::iterator iter = db->begin(); iter != db->end(); iter++) {
    RouteData* routeData = *iter;
    switch (routeData->mRouteType) {
      case RouteData::ProtocolRoute: {
        RouteDataForProtocol* proto = (RouteDataForProtocol*)routeData;
        DLOG_IF(INFO, nfc_debug_enabled)
            << StringPrintf("%s: ee h=0x%X; protocol=0x%X", fn,
                            proto->mNfaEeHandle, proto->mProtocol);
      } break;
      case RouteData::TechnologyRoute: {
        RouteDataForTechnology* tech = (RouteDataForTechnology*)routeData;
        DLOG_IF(INFO, nfc_debug_enabled)
            << StringPrintf("%s: ee h=0x%X; technology=0x%X", fn,
                            tech->mNfaEeHandle, tech->mTechnology);
      } break;
    }
  }

  DLOG_IF(INFO, nfc_debug_enabled)
      << StringPrintf("%s: sec elem route database", fn);
  db = getDatabase(SecElemRouteDatabase);
  for (Database::iterator iter2 = db->begin(); iter2 != db->end(); iter2++) {
    RouteData* routeData = *iter2;
    switch (routeData->mRouteType) {
      case RouteData::ProtocolRoute: {
        RouteDataForProtocol* proto = (RouteDataForProtocol*)routeData;
        DLOG_IF(INFO, nfc_debug_enabled)
            << StringPrintf("%s: ee h=0x%X; protocol=0x%X", fn,
                            proto->mNfaEeHandle, proto->mProtocol);
      } break;
      case RouteData::TechnologyRoute: {
        RouteDataForTechnology* tech = (RouteDataForTechnology*)routeData;
        DLOG_IF(INFO, nfc_debug_enabled)
            << StringPrintf("%s: ee h=0x%X; technology=0x%X", fn,
                            tech->mNfaEeHandle, tech->mTechnology);
      } break;
    }
  }
}