text:
////////////////////////////////////////////////////////////////////////////////
//
// Package:   RoadNarrows Robotics Laelaps Mobile Robot ROS Package
//
// Link:      https://github.com/roadnarrows-robotics/laelaps
//
// ROS Node:  laelaps_control
//
// File:      laelaps_control.cpp
//
/*! \file
 *
 * \brief The ROS laelaps_control node class implementation.
 *
 * \author Robin Knight (robin.knight@roadnarrows.com)
 *
 * \par Copyright:
 * (C) 2015-2017  RoadNarrows
 * (http://www.roadnarrows.com)
 * \n All Rights Reserved
 */
/*
 * @EulaBegin@
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that
 * (1) The above copyright notice and the following two paragraphs
 * appear in all copies of the source code and (2) redistributions
 * including binaries reproduces these notices in the supporting
 * documentation.   Substantial modifications to this software may be
 * copyrighted by their authors and need not follow the licensing terms
 * described here, provided that the new terms are clearly indicated in
 * all files where they apply.
 *
 * IN NO EVENT SHALL THE AUTHOR, ROADNARROWS LLC, OR ANY MEMBERS/EMPLOYEES
 * OF ROADNARROW LLC OR DISTRIBUTORS OF THIS SOFTWARE BE LIABLE TO ANY
 * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
 * DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
 * EVEN IF THE AUTHORS OR ANY OF THE ABOVE PARTIES HAVE BEEN ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * THE AUTHOR AND ROADNARROWS LLC SPECIFICALLY DISCLAIM ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN
 * "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 * @EulaEnd@
 */
////////////////////////////////////////////////////////////////////////////////

//
// System
//
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

#include <limits>
#include <string>
#include <vector>
#include <map>

//
// Boost libraries
//
#include <boost/bind.hpp>
#include <boost/array.hpp>

//
// ROS
//
#include "ros/ros.h"
#include "actionlib/server/simple_action_server.h"

//
// ROS generated core and industrial messages.
//
#include "geometry_msgs/Point.h"
#include "geometry_msgs/Quaternion.h"
#include "geometry_msgs/Twist.h"
#include "industrial_msgs/TriState.h"
#include "industrial_msgs/RobotStatus.h"
#include "sensor_msgs/Illuminance.h"
#include "sensor_msgs/Imu.h"
#include "sensor_msgs/JointState.h"
#include "sensor_msgs/Range.h"

//
// ROS generated Laelaps messages.
//
#include "laelaps_control/Alarms.h"
#include "laelaps_control/AuxPwr.h"
#include "laelaps_control/Caps.h"
#include "laelaps_control/Dimensions.h"
#include "laelaps_control/DutyCycle.h"
#include "laelaps_control/Dynamics.h"
#include "laelaps_control/Gpio.h"
#include "laelaps_control/IlluminanceState.h"
#include "laelaps_control/ImuAlt.h"
#include "laelaps_control/ImuCaps.h"
#include "laelaps_control/MotorCtlrHealth.h"
#include "laelaps_control/MotorHealth.h"
#include "laelaps_control/Path2D.h"
#include "laelaps_control/Pose2DStamped.h"
#include "laelaps_control/PowertrainCaps.h"
#include "laelaps_control/ProductInfo.h"
#include "laelaps_control/RangeState.h"
#include "laelaps_control/RobotStatusExtended.h"
#include "laelaps_control/RobotTrajectory2D.h"
#include "laelaps_control/RobotTrajectoryPoint2D.h"
#include "laelaps_control/ToFSensorCaps.h"
#include "laelaps_control/Velocity.h"

//
// ROS generatated Laelaps services.
//
#include "laelaps_control/ConfigGpio.h"
#include "laelaps_control/EStop.h"
#include "laelaps_control/Freeze.h"
#include "laelaps_control/GetCaps.h"
#include "laelaps_control/GetIlluminance.h"
#include "laelaps_control/GetImu.h"
#include "laelaps_control/GetImuAlt.h"
#include "laelaps_control/GetProductInfo.h"
#include "laelaps_control/GetRange.h"
#include "laelaps_control/IsAlarmed.h"
#include "laelaps_control/IsDescLoaded.h"
#include "laelaps_control/ReadGpio.h"
#include "laelaps_control/Release.h"
#include "laelaps_control/ReloadConfig.h"
#include "laelaps_control/ResetEStop.h"
#include "laelaps_control/SetAuxPwr.h"
#include "laelaps_control/SetDutyCycles.h"
#include "laelaps_control/SetRobotMode.h"
#include "laelaps_control/SetVelocities.h"
#include "laelaps_control/Stop.h"
#include "laelaps_control/WriteGpio.h"

//
// ROS generated action servers.
//

//
// RoadNarrows
//
#include "rnr/rnrconfig.h"
#include "rnr/log.h"

//
// RoadNarrows embedded laelaps library.
//
#include "Laelaps/laelaps.h"
#include "Laelaps/laeUtils.h"
#include "Laelaps/laeXmlCfg.h"
#include "Laelaps/laeTraj.h"
#include "Laelaps/laeImu.h"
#include "Laelaps/laeRobot.h"

//
// Node headers.
//
#include "laelaps_control.h"

using namespace std;
using namespace laelaps;
using namespace laelaps_control;

/*! zero covariance matrix */
static boost::array<double, 9> ZeroCovariance = {0.0, };

//------------------------------------------------------------------------------
// LaelapsControl Class
//------------------------------------------------------------------------------

LaelapsControl::LaelapsControl(ros::NodeHandle &nh, double hz) :
    m_nh(nh), m_hz(hz)
{
}

LaelapsControl::~LaelapsControl()
{
  disconnect();
}

int LaelapsControl::configure(const string &strCfgFile)
{
  LaeXmlCfg xml;  // laelaps xml instance
  int       rc;   // return code

  if((rc = xml.load(m_robot.getLaelapsDesc(), LaeSysCfgPath, strCfgFile)) < 0)
  {
    ROS_ERROR("Loading XML file '%s' failed.", strCfgFile.c_str());
  }

  else if( (rc = m_robot.getLaelapsDesc().markAsDescribed()) < 0 )
  {
    ROS_ERROR("Failed to finalize descriptions.");
  }

  else
  {
    ROS_INFO("Laelaps description loaded:\n\t %s\n\t %s",
       xml.getFileName().c_str(),
       m_robot.getLaelapsDesc().getProdBrief().c_str());
    rc = LAE_OK;
  }

  return rc;
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
// Services
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

void LaelapsControl::advertiseServices()
{
  string  strSvc;

#if 0 // FUTURE MAYBE
  //strSvc = "calibrate";
  //m_services[strSvc] = m_nh.advertiseService(strSvc,
  //                                        &LaelapsControl::calibrate,
  //                                        &(*this));
#endif // FUTURE

  strSvc = "config_gpio";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::configGpio,
                                          &(*this));

  strSvc = "estop";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::estop,
                                          &(*this));

  strSvc = "freeze";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::freeze,
                                          &(*this));

#if 0 // FUTURE
  strSvc = "get_caps";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::getCaps,
                                          &(*this));
#endif // FUTURE

  strSvc = "get_illuminance";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::getIlluminance,
                                          &(*this));

  strSvc = "get_imu";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::getImu,
                                          &(*this));

  strSvc = "get_imu_alt";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::getImuAlt,
                                          this);

  strSvc = "get_range";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::getRange,
                                          &(*this));

  strSvc = "get_product_info";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::getProductInfo,
                                          &(*this));

  strSvc = "is_alarmed";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::isAlarmed,
                                          &(*this));

  strSvc = "is_desc_loaded";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::isDescLoaded,
                                          &(*this));

  strSvc = "read_gpio";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::readGpio,
                                          &(*this));

  strSvc = "release";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::release,
                                          &(*this));

  strSvc = "reload_config";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::reloadConfig,
                                          &(*this));

  strSvc = "reset_estop";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::resetEStop,
                                          &(*this));

  strSvc = "set_aux_power";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::setAuxPower,
                                          &(*this));

  strSvc = "set_duty_cycles";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::setDutyCycles,
                                          &(*this));

  strSvc = "set_robot_mode";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::setRobotMode,
                                          &(*this));

  strSvc = "set_velocities";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::setVelocities,
                                          &(*this));

  strSvc = "stop";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::stop,
                                          &(*this));

  strSvc = "write_gpio";
  m_services[strSvc] = m_nh.advertiseService(strSvc,
                                          &LaelapsControl::writeGpio,
                                          &(*this));
}

bool LaelapsControl::configGpio(ConfigGpio::Request  &req,
                                ConfigGpio::Response &rsp)
{
  const char *svc = "config_gpio";
  int         rc;

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  for(size_t i = 0; i < req.gpio.size(); ++i)
  {
    rc = m_robot.configDigitalPin(req.gpio[i].pin, req.gpio[i].state);

    if( rc == LAE_OK )
    {
      ROS_INFO("Configured GPIO pin %d as %s.",
          req.gpio[i].pin, (req.gpio[i].state? "output": "input"));
    }
    else
    {
      ROS_ERROR("Service %s failed on pin %d: %s(rc=%d).",
          svc, req.gpio[i].pin, getStrError(rc), rc);
      return false;
    }
  }

  return true;
}

bool LaelapsControl::estop(EStop::Request  &req,
                           EStop::Response &rsp)
{
  const char *svc = "estop";

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  m_robot.estop();

  ROS_INFO("ESTOPPED! You must issue a \"reset_estop\" to continue.");

  return true;
}

bool LaelapsControl::freeze(Freeze::Request  &req,
                              Freeze::Response &rsp)
{
  const char *svc = "freeze";

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  m_robot.freeze();

  ROS_INFO("Robot position frozen.");

  return true;
}

bool LaelapsControl::getCaps(GetCaps::Request  &req,
                             GetCaps::Response &rsp)
{
  const char *svc = "get_caps";
  int         rc;

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  if( !m_robot.isDescribed() )
  {
    ROS_ERROR("Service %s failed: "
              "Robot description not loaded - unable to determine info.",
              svc);
    return false;
  }

  // RDK TODO
  // RDK rc = m_robot.getCaps();
  rc = LAE_OK;

  if( rc == LAE_OK )
  {
    ROS_INFO("Robot capabilities retrieved.");
    return true;
  }
  else
  {
    ROS_ERROR("Service %s failed: %s(rc=%d).",
          svc, getStrError(rc), rc);
    return false;
  }
}

bool LaelapsControl::getIlluminance(GetIlluminance::Request  &req,
                                    GetIlluminance::Response &rsp)
{
  const char *svc = "get_illuminance";

  double      fIlluminance;
  int         rc;

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  rc = m_robot.getAmbientLight(req.name, fIlluminance);

  if( rc == LAE_OK )
  {
    stampHeader(rsp.sensor.header, 0);

    rsp.sensor.illuminance = fIlluminance;
    rsp.sensor.variance    = 0;

    ROS_INFO("Ambient light sensor %s = %.1lf lux.",
        req.name.c_str(), rsp.sensor.illuminance);

    return true;
  }
  else
  {
    ROS_ERROR("Service %s failed on sensor %s: %s(rc=%d).",
          svc, req.name.c_str(), getStrError(rc), rc);
    return false;
  }
}

bool LaelapsControl::getImu(GetImu::Request  &req,
                            GetImu::Response &rsp)
{
  const char *svc = "get_imu";

  double                    accel[ImuAlt::NUM_AXES];
  double                    gyro[ImuAlt::NUM_AXES];
  double                    rpy[ImuAlt::NUM_AXES];
  sensor::imu::Quaternion   q;
  int                       rc;

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  rc = m_robot.getImu(accel, gyro, rpy, q);

  if( rc == LAE_OK )
  {
    rsp.imu.orientation.x = q.m_x;
    rsp.imu.orientation.y = q.m_y;
    rsp.imu.orientation.z = q.m_z;
    rsp.imu.orientation.w = q.m_w;

    // for now until known
    rsp.imu.orientation_covariance = ZeroCovariance;

    rsp.imu.angular_velocity.x = gyro[sensor::imu::X];
    rsp.imu.angular_velocity.y = gyro[sensor::imu::Y];
    rsp.imu.angular_velocity.z = gyro[sensor::imu::Z];

    // for now until known
    rsp.imu.angular_velocity_covariance = ZeroCovariance;

    rsp.imu.linear_acceleration.x = accel[sensor::imu::X];
    rsp.imu.linear_acceleration.y = accel[sensor::imu::Y];
    rsp.imu.linear_acceleration.z = accel[sensor::imu::Z];

    // for now until known
    rsp.imu.linear_acceleration_covariance = ZeroCovariance;

    stampHeader(rsp.imu.header, 0);

    ROS_INFO("IMU %s data.", req.name.c_str());
    return true;
  }
  else
  {
    ROS_ERROR("Service %s failed on IMU %s: %s(rc=%d).",
          svc, req.name.c_str(), getStrError(rc), rc);
    return false;
  }
}

bool LaelapsControl::getImuAlt(GetImuAlt::Request  &req,
                               GetImuAlt::Response &rsp)
{
  const char *svc = "get_imu_alt";

  double                    accel[ImuAlt::NUM_AXES];
  double                    gyro[ImuAlt::NUM_AXES];
  double                    rpy[ImuAlt::NUM_AXES];
  sensor::imu::Quaternion   q;
  int                       rc;

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  rc = m_robot.getImu(accel, gyro, rpy, q);

  if( rc == LAE_OK )
  {
    for(int i = 0; i < ImuAlt::NUM_AXES; ++i)
    {
      rsp.imu.accel[i] = accel[i];
      rsp.imu.gyro[i]  = gyro[i];
      rsp.imu.rpy[i]   = rpy[i];
    }

    stampHeader(rsp.imu.header, 0);

    ROS_INFO("IMU %s data.", req.name.c_str());
    return true;
  }
  else
  {
    ROS_ERROR("Service %s failed on IMU %s: %s(rc=%d).",
          svc, req.name.c_str(), getStrError(rc), rc);
    return false;
  }
}

bool LaelapsControl::getProductInfo(GetProductInfo::Request  &req,
                                    GetProductInfo::Response &rsp)
{
  const char *svc = "get_product_info";

  int   nMajor, nMinor, nRev;
  char  s[128];

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  if( !m_robot.isDescribed() )
  {
    ROS_ERROR("%s failed: "
              "Robot description not loaded - unable to determine info.",
              svc);
    return false;
  }

  m_robot.getVersion(nMajor, nMinor, nRev);

  rsp.i.maj             = nMajor;
  rsp.i.min             = nMinor;
  rsp.i.rev             = nRev;
  rsp.i.version_string  = m_robot.getVersionString();
  rsp.i.product_id      = m_robot.getProdId();
  rsp.i.product_name    = m_robot.getProdName();
  rsp.i.desc            = m_robot.getFullProdBrief();

  if( gethostname(s, sizeof(s)) < 0 )
  {
    strcpy(s, "laelaps");
  }
  s[sizeof(s)-1] = 0;

  rsp.i.hostname  = s;

  ROS_INFO("Laelaps %d.%d.%d version.", nMajor, nMinor, nRev);

  return true;
}

bool LaelapsControl::getRange(GetRange::Request  &req,
                              GetRange::Response &rsp)
{
  const char *svc = "get_range";
  string      strRadType;         // sensor radiation type
  double      fFoV;               // sensor field of view (radians)
  double      fBeamDir;           // sensor beam center (radians)
  double      fMin, fMax;         // sensor min and max range (meters)
  double      fRange;             // measured object range (meters)
  int         rc;

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  rc = m_robot.getRangeSensorProps(req.name, strRadType, fFoV, fBeamDir,
                                      fMin, fMax);

  if( rc = LAE_OK )
  {
    rc = m_robot.getRange(req.name, fRange);
  }

  if( rc == LAE_OK )
  {
    if( strRadType == "infrared" )
    {
      rsp.sensor.radiation_type = sensor_msgs::Range::INFRARED;
    }
    else if( strRadType == "ultrasound" )
    {
      rsp.sensor.radiation_type = sensor_msgs::Range::ULTRASOUND;
    }
    else
    {
      rsp.sensor.radiation_type = 0xff;
    }

    rsp.sensor.field_of_view  = fFoV;
    rsp.sensor.min_range      = fMin;
    rsp.sensor.max_range      = fMax;

    if( (fRange < fMin) || (fRange > fMax) )
    {
      rsp.sensor.range = std::numeric_limits<float>::infinity();
    }

    ROS_INFO("Range sensor %s = %.3lf meters.",
        req.name.c_str(), rsp.sensor.range);
    return true;
  }
  else
  {
    ROS_ERROR("Service %s failed on sensor %s: %s(rc=%d).",
          svc, req.name.c_str(), getStrError(rc), rc);
    return false;
  }
}

bool LaelapsControl::isAlarmed(IsAlarmed::Request  &req,
                               IsAlarmed::Response &rsp)
{
  const char *svc = "is_alarmed";

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  rsp.is_alarmed = m_robot.isAlarmed();

  if( rsp.is_alarmed )
  {
    ROS_WARN("Laelaps is alarmed.");
  }

  return true;
}

bool LaelapsControl::isDescLoaded(IsDescLoaded::Request  &req,
                                  IsDescLoaded::Response &rsp)
{
  const char *svc = "is_desc_loaded";

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  rsp.is_desc_loaded = m_robot.getLaelapsDesc().isDescribed();

  if( !rsp.is_desc_loaded )
  {
    ROS_WARN("Laelaps description file not loaded.");
  }

  return true;
}

bool LaelapsControl::readGpio(ReadGpio::Request  &req,
                              ReadGpio::Response &rsp)
{
  const char *svc = "read_gpio";
  int         rc;

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  rsp.gpio.clear();

  for(size_t i = 0; i < req.pin.size(); ++i)
  {
    Gpio    gpio;
    uint_t  val;

    gpio.pin = req.pin[i];

    rc = m_robot.readDigitalPin(gpio.pin, val);

    if( rc == LAE_OK )
    {
      gpio.state = (byte_t)val;
      rsp.gpio.push_back(gpio);

      ROS_INFO("GPIO pin %d = %d.",
          rsp.gpio[i].pin, rsp.gpio[i].state);
    }
    else
    {
      ROS_ERROR("Service %s failed on pin %d: %s(rc=%d).",
          svc, gpio.pin, getStrError(rc), rc);
      return false;
    }
  }

  return true;
}

bool LaelapsControl::release(Release::Request  &req,
                             Release::Response &rsp)
{
  const char *svc = "release";

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  m_robot.release();

  ROS_INFO("Robot released, motors are undriven.");

  return true;
}

bool LaelapsControl::reloadConfig(Release::Request  &req,
                                  Release::Response &rsp)
{
  const char *svc = "reload_config";

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  m_robot.reload();

  ROS_INFO("Robot configuration reloaded.");

  return true;
}

bool LaelapsControl::resetEStop(ResetEStop::Request  &req,
                                ResetEStop::Response &rsp)
{
  const char *svc = "reset_estop";

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  m_robot.resetEStop();

  ROS_INFO("EStop reset.");

  return true;
}

bool LaelapsControl::setAuxPower(SetAuxPwr::Request  &req,
                                 SetAuxPwr::Response &rsp)
{
  const char *svc = "set_aux_power";

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  for(size_t i = 0; i < req.aux.size(); ++i)
  {
    m_robot.setAuxPower(req.aux[i].name, (LaeTriState)req.aux[i].state.val);
    ROS_INFO("Aux. %s power %s.", req.aux[i].name.c_str(),
        (req.aux[i].state.val == industrial_msgs::TriState::ENABLED?
         "enabled": "disabled"));

  }

  return true;
}

bool LaelapsControl::setDutyCycles(SetDutyCycles::Request  &req,
                                   SetDutyCycles::Response &rsp)
{
  const char     *svc = "set_duty_cycles";
  static u32_t    seq = 0;
  LaeMapDutyCycle duties;
  int             rc;

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  for(size_t i = 0; i < req.goal.names.size(); ++i)
  {
    duties[req.goal.names[i]] = req.goal.duties[i];
  }

  rc = m_robot.setDutyCycles(duties);

  if( rc == LAE_OK )
  {
    stampHeader(rsp.actual.header, seq++);

    // RDK TODO get actual goal, not just copy target goal
    rsp.actual = req.goal;

    ROS_INFO("Robot duty cycles set.");
    for(size_t i = 0; i < rsp.actual.names.size(); ++i)
    {
      ROS_INFO(" %-12s: duty=%4.2lf",
          rsp.actual.names[i].c_str(), rsp.actual.duties[i]);
    }
    return true;
  }
  else
  {
    ROS_ERROR("Service %s failed: %s(rc=%d).",
          svc, getStrError(rc), rc);
    return false;
  }
}

bool LaelapsControl::setRobotMode(SetRobotMode::Request  &req,
                                  SetRobotMode::Response &rsp)
{
  const char *svc = "set_robot_mode";

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  m_robot.setRobotMode((LaeRobotMode)req.mode.val);

  ROS_INFO("Robot mode set to %d.", req.mode.val);

  return true;
}

bool LaelapsControl::setVelocities(SetVelocities::Request  &req,
                                   SetVelocities::Response &rsp)
{
  const char     *svc = "set_velocities";
  static u32_t    seq = 0;
  LaeMapVelocity  vel;
  int             rc;

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  for(size_t i = 0; i < req.goal.names.size(); ++i)
  {
    vel[req.goal.names[i]] = req.goal.velocities[i];
  }

  rc = m_robot.move(vel);

  if( rc == LAE_OK )
  {
    stampHeader(rsp.actual.header, seq++);

    // RDK TODO get actual goal, not just copy target goal
    rsp.actual = req.goal;

    ROS_INFO("Robot velocities set.");
    for(size_t i = 0; i < rsp.actual.names.size(); ++i)
    {
      ROS_INFO(" %-12s: vel=%7.2lfdeg/s",
          rsp.actual.names[i].c_str(),
          radToDeg(rsp.actual.velocities[i]));
    }
    return true;
  }
  else
  {
    ROS_ERROR("Service %s failed: %s(rc=%d).",
          svc, getStrError(rc), rc);
    return false;
  }
}

bool LaelapsControl::stop(Stop::Request  &req,
                          Stop::Response &rsp)
{
  const char *svc = "stop";
  int         rc;

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  rc = m_robot.stop();

  if( rc == LAE_OK )
  {
    ROS_INFO("Stopped.");
    return true;
  }
  else
  {
    ROS_ERROR("Service %s failed: %s(rc=%d).", svc, getStrError(rc), rc);
    return false;
  }
}

bool LaelapsControl::writeGpio(WriteGpio::Request  &req,
                               WriteGpio::Response &rsp)
{
  const char *svc = "write_gpio";
  int         rc;

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), svc);

  for(size_t i = 0; i < req.gpio.size(); ++i)
  {
    rc = m_robot.writeDigitalPin(req.gpio[i].pin, req.gpio[i].state);

    if( rc == LAE_OK )
    {
      ROS_INFO("Write GPIO pin %d = %d.",
          req.gpio[i].pin, req.gpio[i].state);
    }
    else
    {
      ROS_ERROR("Service %s failed on pin %d: %s(rc=%d).",
          svc, req.gpio[i].pin, getStrError(rc), rc);
      return false;
    }
  }

  return true;
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
// Topic Publishers
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

void LaelapsControl::advertisePublishers(int nQueueDepth)
{
  string  strPub;

  strPub = "dynamics";
  m_publishers[strPub] =
    m_nh.advertise<Dynamics>(strPub, nQueueDepth);

  strPub = "illuminance_state";
  m_publishers[strPub] =
    m_nh.advertise<IlluminanceState>(strPub, nQueueDepth);

  // topic conforms to the robot_pose_ekf ROS node
  strPub = "imu_data";
  m_publishers[strPub] =
    m_nh.advertise<sensor_msgs::Imu>(strPub, nQueueDepth);

  strPub = "imu_alt_data";
  m_publishers[strPub] =
    m_nh.advertise<ImuAlt>(strPub, nQueueDepth);

#if 0 // FUTURE
  strPub = "joint_state";
  m_publishers[strPub] =
    m_nh.advertise<sensor_msgs::JointState>(strPub, nQueueDepth);
#endif // FUTURE

  strPub = "range_state";
  m_publishers[strPub] =
    m_nh.advertise<RangeState>(strPub, nQueueDepth);

  strPub = "robot_status";
  m_publishers[strPub] =
    m_nh.advertise<industrial_msgs::RobotStatus>(strPub, nQueueDepth);

  strPub = "robot_status_ex";
  m_publishers[strPub] =
    m_nh.advertise<RobotStatusExtended>(strPub, nQueueDepth);
}

void LaelapsControl::publish()
{
  publishDynamics();
  publishRobotStatus();
  publishSensorStates();
}

void LaelapsControl::publishDynamics()
{
  // update dynamics message
  updateDynamicsMsg(m_msgDynamics);

  // publish dynamics messages
  m_publishers["dynamics"].publish(m_msgDynamics);

#if 0 // FUTURE
  updateJointStateMsg(..., m_msgJointState);

  // publish message
  m_publishers["joint_state"].publish(m_msgJointState);
#endif // FUTURE
}

void LaelapsControl::publishRobotStatus()
{
  LaeRptRobotStatus status;   // really status

  // get robot's extended status.
  m_robot.getRobotStatus(status);

  // update robot status message
  updateRobotStatusMsg(status, m_msgRobotStatus);

  // publish robot status message
  m_publishers["robot_status"].publish(m_msgRobotStatus);

  // update extended robot status message
  updateExtendedRobotStatusMsg(status, m_msgRobotStatusEx);

  // publish extened robot status message
  m_publishers["robot_status_ex"].publish(m_msgRobotStatusEx);
}

void LaelapsControl::publishSensorStates()
{
  updateIlluminanceStateMsg(m_msgIlluminanceState);

  m_publishers["illuminance_state"].publish(m_msgIlluminanceState);

  publishImuStates();

  updateRangeStateMsg(m_msgRangeState);

  m_publishers["range_state"].publish(m_msgRangeState);
}

void LaelapsControl::updateDynamicsMsg(Dynamics &msg)
{
  LaeRptDynamics  dynamics;

  // get robot's dynamics
  m_robot.getDynamics(dynamics);

  //
  // Clear previous state data.
  //
  msg.name.clear();
  msg.position.clear();
  msg.velocity.clear();
  msg.torque.clear();
  msg.motor_encoder.clear();
  msg.motor_speed.clear();
  msg.motor_power_elec.clear();

  //
  // Set header.
  //
  stampHeader(msg.header, msg.header.seq+1);

  //
  // Set robot values.
  //
  msg.robot_pose.x      =  dynamics.m_pose.m_x;
  msg.robot_pose.y      =  dynamics.m_pose.m_y;
  msg.robot_pose.theta  =  dynamics.m_pose.m_theta;
  msg.robot_odometer    =  dynamics.m_fOdometer;
  msg.robot_velocity    =  dynamics.m_fVelocity;

  //
  // Set powertrain values.
  //
  for(size_t i = 0; i < dynamics.m_vecDynPowertrain.size(); ++i)
  {
    LaeRptDynPowertrain &pt = dynamics.m_vecDynPowertrain[i];

    msg.name.push_back(pt.m_strName);
    msg.position.push_back(pt.m_fPosition);
    msg.velocity.push_back(pt.m_fVelocity);
    msg.torque.push_back(pt.m_fTorque);
    msg.motor_encoder.push_back(pt.m_nEncoder);
    msg.motor_speed.push_back(pt.m_nSpeed);
    msg.motor_power_elec.push_back(pt.m_fPe);
  }
}

void LaelapsControl::updateRobotStatusMsg(LaeRptRobotStatus &status,
                                          industrial_msgs::RobotStatus &msg)
{
  //
  // Set header.
  //
  stampHeader(msg.header, msg.header.seq+1);

  //
  // Set industrial message compliant robot status values.
  //
  msg.mode.val            = status.m_eRobotMode;
  msg.e_stopped.val       = status.m_eIsEStopped;
  msg.drives_powered.val  = status.m_eAreDrivesPowered;
  msg.motion_possible.val = status.m_eIsMotionPossible;
  msg.in_motion.val       = status.m_eIsInMotion;
  msg.in_error.val        = status.m_eIsInError;
  msg.error_code          = status.m_nErrorCode;

}

void LaelapsControl::updateExtendedRobotStatusMsg(LaeRptRobotStatus &status,
                                                  RobotStatusExtended &msg)
{
  int         i;

  // clear previous data
  msg.motor_ctlr_health.clear();
  msg.motor_health.clear();

  //
  // Set header.
  //
  stampHeader(msg.header, msg.header.seq+1);

  //
  // Set industrial message compliant robot status values.
  //
  msg.mode.val            = status.m_eRobotMode;
  msg.e_stopped.val       = status.m_eIsEStopped;
  msg.drives_powered.val  = status.m_eAreDrivesPowered;
  msg.motion_possible.val = status.m_eIsMotionPossible;
  msg.in_motion.val       = status.m_eIsInMotion;
  msg.in_error.val        = status.m_eIsInError;
  msg.error_code          = status.m_nErrorCode;

  //
  // Set extended robot status values.
  //
  msg.battery         = status.m_fBatterySoC;
  msg.is_charging.val = status.m_bIsCharging;
  msg.current         = status.m_fCurrent;
  msg.voltage         = status.m_fVoltage;
  msg.temp            = status.m_fTemperature;
  msg.aux_batt_en.val = status.m_eAuxBattEn;
  msg.aux_5v_en.val   = status.m_eAux5VEn;

  msg.alarms.is_critical  = status.m_alarms.m_bIsCritical;
  msg.alarms.alarms       = status.m_alarms.m_uAlarms;
  msg.alarms.warnings     = status.m_alarms.m_uWarnings;

  for(size_t i = 0; i < status.m_vecCtlrHealth.size(); ++i)
  {
    LaeRptMotorCtlrHealth  &h = status.m_vecCtlrHealth[i];
    MotorCtlrHealth         health;

    health.name     = h.m_strName;
    health.temp     = h.m_fTemperature;
    health.voltage  = h.m_fVoltage;

    health.alarms.is_critical = h.m_alarms.m_bIsCritical;
    health.alarms.alarms      = h.m_alarms.m_uAlarms;
    health.alarms.warnings    = h.m_alarms.m_uWarnings;

    msg.motor_ctlr_health.push_back(health);
  }

  for(size_t i = 0; i < status.m_vecMotorHealth.size(); ++i)
  {
    LaeRptMotorHealth  &h = status.m_vecMotorHealth[i];
    MotorHealth         health;

    health.name     = h.m_strName;
    health.temp     = h.m_fTemperature;
    health.voltage  = h.m_fVoltage;
    health.current  = h.m_fCurrent;

    health.alarms.is_critical = h.m_alarms.m_bIsCritical;
    health.alarms.alarms      = h.m_alarms.m_uAlarms;
    health.alarms.warnings    = h.m_alarms.m_uWarnings;

    msg.motor_health.push_back(health);
  }
}

void LaelapsControl::updateIlluminanceStateMsg(IlluminanceState &msg)
{
  vector<string>  names;
  vector<double>  lux;
  int             rc;

  msg.name.clear();
  msg.illuminance.clear();

  rc = m_robot.getAmbientLight(names, lux);

  if( rc == LAE_OK )
  {
    stampHeader(msg.header, msg.header.seq+1);

    for(size_t i = 0; i < names.size(); ++i )
    {
      msg.name.push_back(names[i]);
      msg.illuminance.push_back(lux[i]);
    }
  }
}

void LaelapsControl::publishImuStates()
{
  double                    accel[ImuAlt::NUM_AXES];
  double                    gyro[ImuAlt::NUM_AXES];
  double                    rpy[ImuAlt::NUM_AXES];
  sensor::imu::Quaternion   q;
  int                       rc;

  //
  // Grab latest IMU data
  //
  if( (rc = m_robot.getImu(accel, gyro, rpy, q)) != LAE_OK )
  {
    return;
  }

  //
  // Convert to ROS standard IMU message
  //
  m_msgImu.orientation.x = q.m_x;
  m_msgImu.orientation.y = q.m_y;
  m_msgImu.orientation.z = q.m_z;
  m_msgImu.orientation.w = q.m_w;

    // for now until known
  m_msgImu.orientation_covariance = ZeroCovariance;

  m_msgImu.angular_velocity.x = gyro[sensor::imu::X];
  m_msgImu.angular_velocity.y = gyro[sensor::imu::Y];
  m_msgImu.angular_velocity.z = gyro[sensor::imu::Z];

    // for now until known
  m_msgImu.angular_velocity_covariance = ZeroCovariance;

  m_msgImu.linear_acceleration.x = accel[sensor::imu::X];
  m_msgImu.linear_acceleration.y = accel[sensor::imu::Y];
  m_msgImu.linear_acceleration.z = accel[sensor::imu::Z];

    // for now until known
  m_msgImu.linear_acceleration_covariance = ZeroCovariance;

  stampHeader(m_msgImu.header, m_msgImu.header.seq+1);

  //
  // Convert to alternative IMU ROS message
  //
  for(int i = 0; i < ImuAlt::NUM_AXES; ++i)
  {
    m_msgImuAlt.accel[i] = accel[i];
    m_msgImuAlt.gyro[i]  = gyro[i];
    m_msgImuAlt.rpy[i]   = rpy[i];
  }

  stampHeader(m_msgImuAlt.header, m_msgImuAlt.header.seq+1);

  //
  // Publish messages
  //
  m_publishers["imu_data"].publish(m_msgImu);
  m_publishers["imu_alt_data"].publish(m_msgImuAlt);
}

void LaelapsControl::updateRangeStateMsg(RangeState &msg)
{
  vector<string>  names;
  vector<double>  ranges;
  int             rc;

  msg.name.clear();
  msg.range.clear();

  rc = m_robot.getRange(names, ranges);

  if( rc == LAE_OK )
  {
    stampHeader(msg.header, msg.header.seq+1);

    for(size_t i = 0; i < names.size(); ++i )
    {
      msg.name.push_back(names[i]);
      msg.range.push_back(ranges[i]);
    }
  }
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
// Subscribed Topics
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

void LaelapsControl::subscribeToTopics(int nQueueDepth)
{
  string  strSub;

  // laelaps specific velocity
  strSub = "cmd_motor_duties";
  m_subscriptions[strSub] = m_nh.subscribe(strSub, nQueueDepth,
                                          &LaelapsControl::execCmdDutyCycles,
                                          &(*this));

  // standard topic used in various ROS nodes for the twist message
  strSub = "cmd_vel";
  m_subscriptions[strSub] = m_nh.subscribe(strSub, nQueueDepth,
                                          &LaelapsControl::execCmdTwist,
                                          &(*this));

  strSub = "cmd_wheel_velocities";
  m_subscriptions[strSub] = m_nh.subscribe(strSub, nQueueDepth,
                                          &LaelapsControl::execCmdVelocities,
                                          &(*this));
}

void LaelapsControl::execCmdDutyCycles(const laelaps_control::DutyCycle
                                                                      &msgDuty)
{
  const char     *topic = "cmd_motor_duties";
  LaeMapDutyCycle duties;

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), topic);

  // set velocities
  ROS_INFO("%s", topic);
  for(int i=0; i<msgDuty.names.size(); ++i)
  {
    duties[msgDuty.names[i]] = msgDuty.duties[i];
    ROS_INFO(" %-12s: duty=%4.2lf",
        msgDuty.names[i].c_str(), msgDuty.duties[i]);
  }

  m_robot.setDutyCycles(duties);
}

void LaelapsControl::execCmdTwist(const geometry_msgs::Twist &msgTwist)
{
  const char     *topic = "cmd_vel";

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), topic);

  // set twist
  ROS_INFO("%s", topic);

  double velLin = msgTwist.linear.x;     // x = forward/backward
  double velAng = msgTwist.angular.z;    // yaw = left/right turn

  ROS_INFO("vel_linear=%lf m/s, vel_angular=%lf deg/s",
        velLin, radToDeg(velAng));

  m_robot.move(velLin, velAng);
}

void LaelapsControl::execCmdVelocities(const laelaps_control::Velocity &msgVel)
{
  const char     *topic = "cmd_wheel_velocities";
  LaeMapVelocity  vel;

  ROS_DEBUG("%s/%s", m_nh.getNamespace().c_str(), topic);

  // set velocities
  ROS_INFO("%s", topic);
  for(int i=0; i<msgVel.names.size(); ++i)
  {
    vel[msgVel.names[i]] = msgVel.velocities[i];
    ROS_INFO(" %-12s: vel=%7.2lfdeg/s",
        msgVel.names[i].c_str(), radToDeg(msgVel.velocities[i]));
  }

  m_robot.move(vel);
}

void LaelapsControl::stampHeader(std_msgs::Header &header,
                                 u32_t             nSeqNum,
                                 const string     &strFrameId)
{
  header.seq      = nSeqNum;
  header.stamp    = ros::Time::now();
  header.frame_id = strFrameId;
}

max_stars_repo_name: roadnarrows-robotics/laelaps

max_issues_repo_name: roadnarrows-robotics/laelaps

max_forks_repo_name: roadnarrows-robotics/laelaps


text:
// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <base58.h>
#include <chainparams.h>
#include <sidechain.h>
#include <sidechaindb.h>
#include <validation.h>

#include <test/test_drivechain.h>

#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(sidechainactivation_tests, TestingSetup)

BOOST_AUTO_TEST_CASE(proposal_single)
{
    // Test adding one proposal to scdbTest
    SidechainDB scdbTest;

    // Create sidechain proposal
    Sidechain proposal;
    proposal.nSidechain = 0;
    proposal.title = "test";
    proposal.description = "description";
    proposal.hashID1 = uint256S("b55d224f1fda033d930c92b1b40871f209387355557dd5e0d2b5dd9bb813c33f");
    proposal.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    // Create transaction output with sidechain proposal
    CTxOut out;
    out.scriptPubKey = proposal.GetProposalScript();
    out.nValue = 50 * CENT;

    BOOST_CHECK(out.scriptPubKey.IsSidechainProposalCommit());

    // Updated scdbTest to add the proposal
    uint256 hash = GetRandHash();
    scdbTest.Update(0, hash, uint256(), std::vector<CTxOut>{out});

    std::vector<SidechainActivationStatus> vActivation;
    vActivation = scdbTest.GetSidechainActivationStatus();

    // Verify scdbTest is tracking the proposal
    BOOST_CHECK((vActivation.size() == 1) && (vActivation.front().proposal.GetProposalScript() == proposal.GetProposalScript()));
}

BOOST_AUTO_TEST_CASE(proposal_multiple)
{
    // Test adding multiple proposal to scdbTest
    SidechainDB scdbTest;

    // Create sidechain proposal
    Sidechain proposal1;
    proposal1.nSidechain = 0;
    proposal1.title = "test1";
    proposal1.description = "description";
    proposal1.hashID1 = uint256S("b55d224f1fda033d930c92b1b40871f209387355557dd5e0d2b5dd9bb813c33f");
    proposal1.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    // Create transaction output with sidechain proposal
    CTxOut out;
    out.scriptPubKey = proposal1.GetProposalScript();
    out.nValue = 50 * CENT;

    BOOST_CHECK(out.scriptPubKey.IsSidechainProposalCommit());

    // Update scdbTest to add the proposal
    uint256 hash1 = GetRandHash();
    scdbTest.Update(0, hash1, uint256(), std::vector<CTxOut>{out});

    // Create sidechain proposal
    Sidechain proposal2;
    proposal2.nSidechain = 1;
    proposal2.title = "test2";
    proposal2.description = "description";
    proposal2.hashID1 = uint256S("b55d224f1fda033d930c92b1b40871f209387355557dd5e0d2b5dd9bb813c33f");
    proposal2.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    // Create transaction output with sidechain proposal
    CTxOut out2;
    out2.scriptPubKey = proposal2.GetProposalScript();
    out2.nValue = 50 * CENT;

    BOOST_CHECK(out2.scriptPubKey.IsSidechainProposalCommit());

    // Update scdbTest to add the proposal
    scdbTest.Update(1, GetRandHash(), hash1, std::vector<CTxOut>{out2});

    std::vector<SidechainActivationStatus> vActivation;
    vActivation = scdbTest.GetSidechainActivationStatus();

    // Verify scdbTest is tracking the proposals
    BOOST_CHECK((vActivation.size() == 2) &&
            (vActivation.front().proposal.GetProposalScript() == proposal1.GetProposalScript()) &&
            (vActivation.back().proposal.GetProposalScript() == proposal2.GetProposalScript()));
}

BOOST_AUTO_TEST_CASE(proposal_perblock_limit)
{
    // Make sure multiple sidechain proposals in one block will be rejected.
    SidechainDB scdbTest;

    // Create sidechain proposal
    Sidechain proposal1;
    proposal1.nSidechain = 0;
    proposal1.title = "test1";
    proposal1.description = "description";
    proposal1.hashID1 = uint256S("b55d224f1fda033d930c92b1b40871f209387355557dd5e0d2b5dd9bb813c33f");
    proposal1.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    // Create transaction output with sidechain proposal
    CTxOut out;
    out.scriptPubKey = proposal1.GetProposalScript();
    out.nValue = 50 * CENT;

    BOOST_CHECK(out.scriptPubKey.IsSidechainProposalCommit());

    // Create sidechain proposal
    Sidechain proposal2;
    proposal2.nSidechain = 1;
    proposal2.title = "test2";
    proposal2.description = "description";
    proposal2.hashID1 = uint256S("b55d224f1fda033d930c92b1b40871f209387355557dd5e0d2b5dd9bb813c33f");
    proposal2.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    // Create transaction output with sidechain proposal
    CTxOut out2;
    out2.scriptPubKey = proposal2.GetProposalScript();
    out2.nValue = 50 * CENT;

    BOOST_CHECK(out2.scriptPubKey.IsSidechainProposalCommit());

    // Update scdbTest to add the proposal
    scdbTest.Update(0, GetRandHash(), uint256(), std::vector<CTxOut>{out, out2});

    std::vector<SidechainActivationStatus> vActivation;
    vActivation = scdbTest.GetSidechainActivationStatus();

    // Nothing should have been added
    BOOST_CHECK(vActivation.empty());
}

BOOST_AUTO_TEST_CASE(activate_single)
{
    SidechainDB scdbTest;

    // Create sidechain proposal
    Sidechain proposal;
    proposal.nSidechain = 0;
    proposal.nVersion = 0;
    proposal.title = "test";
    proposal.description = "description";
    proposal.hashID1 = uint256S("b55d224f1fda033d930c92b1b40871f209387355557dd5e0d2b5dd9bb813c33f");
    proposal.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == 0);
    BOOST_CHECK(ActivateSidechain(scdbTest, proposal, 0));
    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == 1);
}

BOOST_AUTO_TEST_CASE(activate_multiple)
{
    SidechainDB scdbTest;

    Sidechain proposal1;
    proposal1.nSidechain = 0;
    proposal1.nVersion = 0;
    proposal1.title = "sidechain1";
    proposal1.description = "description";
    proposal1.hashID1 = uint256S("b55d224f1fda033d930c92b1b40871f209387355557dd5e0d2b5dd9bb813c33f");
    proposal1.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == 0);
    BOOST_CHECK(ActivateSidechain(scdbTest, proposal1, 0));
    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == 1);

    // Proposal for a second sidechain
    Sidechain proposal2;
    proposal2.nSidechain = 1;
    proposal2.nVersion = 0;
    proposal2.title = "sidechain2";
    proposal2.description = "test";
    proposal2.hashID1 = GetRandHash();
    proposal2.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    BOOST_CHECK(ActivateSidechain(scdbTest, proposal2, 0));
    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == 2);

    // Copy sidechain 2
    Sidechain proposal3 = proposal2;
    proposal3.nSidechain = 2;
    proposal3.title = "abc";

    BOOST_CHECK(ActivateSidechain(scdbTest, proposal3, 0));
    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == 3);
}

BOOST_AUTO_TEST_CASE(activate_max)
{
    SidechainDB scdbTest;

    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == 0);

    Sidechain proposal;
    proposal.nVersion = 0;
    proposal.title = "sidechain";
    proposal.description = "test";
    proposal.hashID1 = GetRandHash();
    proposal.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    unsigned int nSidechains = 0;
    for (int i = 0; i < SIDECHAIN_ACTIVATION_MAX_ACTIVE; i++) {
        proposal.nSidechain = i;
        proposal.title = "sidechain" + std::to_string(i);

        BOOST_CHECK(ActivateSidechain(scdbTest, proposal, 0));

        nSidechains++;

        BOOST_CHECK(scdbTest.GetActiveSidechainCount() == nSidechains);
    }

    // Check that the maximum number have been activated
    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == SIDECHAIN_ACTIVATION_MAX_ACTIVE);

    std::vector<Sidechain> vSidechain = scdbTest.GetSidechains();
    BOOST_CHECK(vSidechain.size() == SIDECHAIN_ACTIVATION_MAX_ACTIVE);

    // Check sidechain numbers and active status
    for (size_t i = 0; i < vSidechain.size(); i++) {
        BOOST_CHECK(vSidechain[i].fActive == true);
        BOOST_CHECK(vSidechain[i].nSidechain == i);
    }
}

BOOST_AUTO_TEST_CASE(activate_fail)
{
    // Test adding one proposal to scdbTest and fail to activate it
    SidechainDB scdbTest;

    // Create sidechain proposal
    Sidechain proposal;
    proposal.nSidechain = 0;
    proposal.nVersion = 0;
    proposal.title = "test";
    proposal.description = "description";
    proposal.hashID1 = uint256S("b55d224f1fda033d930c92b1b40871f209387355557dd5e0d2b5dd9bb813c33f");
    proposal.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    // Create transaction output with sidechain proposal
    CTxOut out;
    out.scriptPubKey = proposal.GetProposalScript();
    out.nValue = 50 * CENT;

    BOOST_CHECK(out.scriptPubKey.IsSidechainProposalCommit());

    uint256 hash1 = GetRandHash();
    scdbTest.Update(0, hash1, uint256(), std::vector<CTxOut>{out});

    std::vector<SidechainActivationStatus> vActivation;
    vActivation = scdbTest.GetSidechainActivationStatus();

    BOOST_CHECK((vActivation.size() == 1) && (vActivation.front().proposal.GetProposalScript() == proposal.GetProposalScript()));

    // Use the function from validation to generate the commit, and then
    // copy it from the block.
    CBlock block;
    CMutableTransaction mtx;
    mtx.vin.resize(1);
    mtx.vin[0].prevout.SetNull();
    block.vtx.push_back(MakeTransactionRef(std::move(mtx)));
    GenerateSidechainActivationCommitment(block, proposal.GetHash());

    // Add votes until the sidechain is half way activated
    for (int i = 1; i <= SIDECHAIN_ACTIVATION_PERIOD / 2; i++) {
        uint256 hash2 = GetRandHash();
        scdbTest.Update(i, hash2, hash1, block.vtx.front()->vout);
        hash1 = hash2;
    }

    // Check activation status
    // Sidechain should still be in activation cache
    // Sidechain should not be in ValidSidechains
    std::vector<Sidechain> vSidechain = scdbTest.GetActiveSidechains();

    BOOST_CHECK(vSidechain.empty());
}

BOOST_AUTO_TEST_CASE(activate_remove_failed)
{
    // Test that sidechains which have no chance of success (based on their
    // rejection count) are pruned from the activation cache.
    SidechainDB scdbTest;

    // Create sidechain proposal
    Sidechain proposal;
    proposal.nSidechain = 0;
    proposal.nVersion = 0;
    proposal.title = "test";
    proposal.description = "description";
    proposal.hashID1 = uint256S("b55d224f1fda033d930c92b1b40871f209387355557dd5e0d2b5dd9bb813c33f");
    proposal.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    // Create transaction output with sidechain proposal
    CTxOut out;
    out.scriptPubKey = proposal.GetProposalScript();
    out.nValue = 50 * CENT;

    BOOST_CHECK(out.scriptPubKey.IsSidechainProposalCommit());

    uint256 hash1 = GetRandHash();
    scdbTest.Update(0, hash1, uint256(), std::vector<CTxOut>{out});

    std::vector<SidechainActivationStatus> vActivation;
    vActivation = scdbTest.GetSidechainActivationStatus();

    BOOST_CHECK((vActivation.size() == 1) && (vActivation.front().proposal.GetProposalScript() == proposal.GetProposalScript()));

    // Pass coinbase without sidechain activation commit into scdbTest enough times
    // that the proposal is rejected and pruned.
    out.scriptPubKey = OP_TRUE;
    out.nValue = 50 * CENT;

    for (int i = 1; i <= SIDECHAIN_ACTIVATION_MAX_FAILURES + 1; i++) {
        vActivation = scdbTest.GetSidechainActivationStatus();
        uint256 hash2 = GetRandHash();
        scdbTest.Update(i, hash2, hash1, std::vector<CTxOut>{out});
        hash1 = hash2;
    }

    // Check activation status
    // Sidechain should have been pruned from activation cache
    // Sidechain should not be in ValidSidechains
    std::vector<SidechainActivationStatus> vActivationFinal;
    vActivationFinal = scdbTest.GetSidechainActivationStatus();
    BOOST_CHECK(vActivationFinal.empty());

    std::vector<Sidechain> vSidechain = scdbTest.GetActiveSidechains();
    BOOST_CHECK(vSidechain.empty());
}

BOOST_AUTO_TEST_CASE(none_active)
{
    // Test that when no sidechains have been activated, the sidechain list
    // lists all of them with inactive status and the correct sidechain number.
    SidechainDB scdbTest;

    // No sidechains should be active
    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == 0);

    std::vector<Sidechain> vActive = scdbTest.GetActiveSidechains();
    BOOST_CHECK(vActive.empty());

    std::vector<Sidechain> vSidechain = scdbTest.GetSidechains();
    BOOST_CHECK(vSidechain.size() == SIDECHAIN_ACTIVATION_MAX_ACTIVE);

    // Check sidechain numbers and active status
    for (size_t i = 0; i < vSidechain.size(); i++) {
        BOOST_CHECK(vSidechain[i].fActive == false);
        BOOST_CHECK(vSidechain[i].nSidechain == i);
    }
}

BOOST_AUTO_TEST_CASE(max_active_reverse)
{
    // Test activating the maximum number of sidechains but in reverse order
    // from sidechain #255 to #0.
    SidechainDB scdbTest;

    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == 0);

    Sidechain proposal;
    proposal.nVersion = 0;
    proposal.description = "test";
    proposal.hashID1 = GetRandHash();
    proposal.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    unsigned int nSidechains = 0;
    for (int i = SIDECHAIN_ACTIVATION_MAX_ACTIVE - 1; i >= 0; i--) {
        proposal.nSidechain = i;
        proposal.title = "sidechain" + std::to_string(i);

        BOOST_CHECK(ActivateSidechain(scdbTest, proposal, 0));

        nSidechains++;

        BOOST_CHECK(scdbTest.GetActiveSidechainCount() == nSidechains);
    }

    // Check that the maximum number have been activated
    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == SIDECHAIN_ACTIVATION_MAX_ACTIVE);

    std::vector<Sidechain> vSidechain = scdbTest.GetSidechains();
    BOOST_CHECK(vSidechain.size() == SIDECHAIN_ACTIVATION_MAX_ACTIVE);

    // Check sidechain numbers and active status
    for (size_t i = 0; i < vSidechain.size(); i++) {
        BOOST_CHECK(vSidechain[i].fActive == true);
        BOOST_CHECK(vSidechain[i].nSidechain == i);
    }
}

BOOST_AUTO_TEST_CASE(every_other_active)
{
    // Test activating half of the maximum number of sidechains skipping one
    // sidechain slot between each activated sidechain.
    SidechainDB scdbTest;

    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == 0);

    // TODO sidechains with the same key and IDs should be rejected
    Sidechain proposal;
    proposal.nVersion = 0;
    proposal.description = "test";
    proposal.hashID1 = GetRandHash();
    proposal.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    unsigned int nSidechains = 0;
    for (int i = 0; i < SIDECHAIN_ACTIVATION_MAX_ACTIVE; i++) {
        if (i % 2 == 0)
            continue;

        proposal.nSidechain = i;
        proposal.title = "sidechain" + std::to_string(i);

        BOOST_CHECK(ActivateSidechain(scdbTest, proposal, 0));

        nSidechains++;

        BOOST_CHECK(scdbTest.GetActiveSidechainCount() == nSidechains);
    }

    // Check that half of the maximum number have been activated
    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == SIDECHAIN_ACTIVATION_MAX_ACTIVE / 2);

    std::vector<Sidechain> vSidechain = scdbTest.GetSidechains();
    BOOST_CHECK(vSidechain.size() == SIDECHAIN_ACTIVATION_MAX_ACTIVE);

    // Check sidechain numbers and active status
    for (size_t i = 0; i < vSidechain.size(); i++) {
        if (i % 2 == 0)
            BOOST_CHECK(vSidechain[i].fActive == false);
        else
            BOOST_CHECK(vSidechain[i].fActive == true);

        BOOST_CHECK(vSidechain[i].nSidechain == i);
    }
}

BOOST_AUTO_TEST_CASE(replace_sidechain)
{
    SidechainDB scdbTest;

    // Activate first sidechain

    // Create sidechain proposal
    Sidechain proposal;
    proposal.nSidechain = 0;
    proposal.nVersion = 0;
    proposal.title = "test";
    proposal.description = "description";
    proposal.hashID1 = uint256S("b55d224f1fda033d930c92b1b40871f209387355557dd5e0d2b5dd9bb813c33f");
    proposal.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == 0);
    BOOST_CHECK(ActivateSidechain(scdbTest, proposal, 0));
    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == 1);

    // Create replacement sidechain proposal

    // Create sidechain proposal
    Sidechain proposal2;
    proposal2.nSidechain = 0;
    proposal2.nVersion = 0;
    proposal2.title = "replacement";
    proposal2.description = "description";
    proposal2.hashID1 = uint256S("ff5d224f1fda033d930c92b1b40871f209387355557dd5e0d2b5dd9bb813c33f");
    proposal2.hashID2 = uint160S("ffd98584f3c570961359c308619f5cf2e9178482");

    // Create transaction output with sidechain proposal
    CTxOut out;
    out.scriptPubKey = proposal2.GetProposalScript();
    out.nValue = 50 * CENT;

    BOOST_CHECK(out.scriptPubKey.IsSidechainProposalCommit());

    uint256 hash1 = GetRandHash();
    BOOST_CHECK(scdbTest.Update(0, hash1, scdbTest.GetHashBlockLastSeen(), std::vector<CTxOut>{out}));

    std::vector<SidechainActivationStatus> vActivation;
    vActivation = scdbTest.GetSidechainActivationStatus();

    BOOST_CHECK((vActivation.size() == 1) && (vActivation.front().proposal.GetProposalScript() == proposal2.GetProposalScript()));

    // Generate a sidechain activation commit for the replacement
    CBlock block;
    CMutableTransaction mtx;
    mtx.vin.resize(1);
    mtx.vin[0].prevout.SetNull();
    block.vtx.push_back(MakeTransactionRef(std::move(mtx)));
    GenerateSidechainActivationCommitment(block, vActivation.front().proposal.GetHash());

    // Add the requirement to replace
    for (int i = 1; i <= SIDECHAIN_REPLACEMENT_PERIOD - 1; i++)
        BOOST_CHECK(scdbTest.Update(i, GetRandHash(), scdbTest.GetHashBlockLastSeen(), block.vtx.front()->vout));

    // Check that the proposal has half of the replacement requirement
    vActivation = scdbTest.GetSidechainActivationStatus();

    // Check activation status
    // replacement should have been pruned from activation cache
    std::vector<SidechainActivationStatus> vActivationFinal;
    vActivationFinal = scdbTest.GetSidechainActivationStatus();
    BOOST_CHECK(vActivationFinal.empty());

    // Sidechain 0 should now be "replacement"
    // Check that "replacement" sidechain was activated
    std::vector<Sidechain> vSidechain = scdbTest.GetSidechains();
    BOOST_CHECK(vSidechain[0].title == "replacement");
}

BOOST_AUTO_TEST_CASE(replace_sidechain_fail)
{
    SidechainDB scdbTest;

    // Activate first sidechain

    // Create sidechain proposal
    Sidechain proposal;
    proposal.nSidechain = 0;
    proposal.nVersion = 0;
    proposal.title = "test";
    proposal.description = "description";
    proposal.hashID1 = uint256S("b55d224f1fda033d930c92b1b40871f209387355557dd5e0d2b5dd9bb813c33f");
    proposal.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == 0);
    BOOST_CHECK(ActivateSidechain(scdbTest, proposal, 0));
    BOOST_CHECK(scdbTest.GetActiveSidechainCount() == 1);

    // Create replacement sidechain proposal

    // Create sidechain proposal
    Sidechain proposal2;
    proposal2.nSidechain = 0;
    proposal2.nVersion = 0;
    proposal2.title = "replacement";
    proposal2.description = "description";
    proposal2.hashID1 = uint256S("b55d224f1fda033d930c92b1b40871f209387355557dd5e0d2b5dd9bb813c33f");
    proposal2.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    // Create transaction output with sidechain proposal
    CTxOut out;
    out.scriptPubKey = proposal2.GetProposalScript();
    out.nValue = 50 * CENT;

    BOOST_CHECK(out.scriptPubKey.IsSidechainProposalCommit());

    BOOST_CHECK(scdbTest.Update(0, GetRandHash(), scdbTest.GetHashBlockLastSeen(), std::vector<CTxOut>{out}));

    std::vector<SidechainActivationStatus> vActivation;
    vActivation = scdbTest.GetSidechainActivationStatus();

    BOOST_CHECK((vActivation.size() == 1) && (vActivation.front().proposal.GetProposalScript() == proposal2.GetProposalScript()));

    // Generate a sidechain activation commit for the replacement
    CBlock block;
    CMutableTransaction mtx;
    mtx.vin.resize(1);
    mtx.vin[0].prevout.SetNull();
    block.vtx.push_back(MakeTransactionRef(std::move(mtx)));
    GenerateSidechainActivationCommitment(block, vActivation.front().proposal.GetHash());

    // Add half of the requirement to replace
    for (int i = 1; i <= SIDECHAIN_REPLACEMENT_PERIOD / 2; i++)
        BOOST_CHECK(scdbTest.Update(i, GetRandHash(), scdbTest.GetHashBlockLastSeen(), block.vtx.front()->vout));

    // Generate blocks without activation commitments to make proposal fail

    out.scriptPubKey = OP_TRUE;
    out.nValue = 50 * CENT;

    for (int i = 1; i <= SIDECHAIN_ACTIVATION_MAX_FAILURES + 1; i++)
        BOOST_CHECK(scdbTest.Update(i, GetRandHash(), scdbTest.GetHashBlockLastSeen(), std::vector<CTxOut>{out}));

    // Check activation status
    // replacement should have been pruned from activation cache
    // replacement should not be active
    std::vector<SidechainActivationStatus> vActivationFinal;
    vActivationFinal = scdbTest.GetSidechainActivationStatus();
    BOOST_CHECK(vActivationFinal.empty());

    // Check that "replacement" sidechain was not activated
    // Sidechain 0 should still be "test"
    std::vector<Sidechain> vSidechain = scdbTest.GetSidechains();
    BOOST_CHECK(vSidechain[0].title == "test");
}

BOOST_AUTO_TEST_CASE(per_block_activation_limit_pass)
{
    // Test that only one sidechain activation commit is allowed for each
    // sidechain number per block. In this test we will ACK two sidechains that
    // have different sidechain numbers, which should be allowed.
    SidechainDB scdbTest;

    Sidechain proposal1;
    proposal1.nSidechain = 0;
    proposal1.nVersion = 0;
    proposal1.title = "sidechain1";
    proposal1.description = "description";
    proposal1.hashID1 = uint256S("b55d224f1fda033d930c92b1b40871f209387355557dd5e0d2b5dd9bb813c33f");
    proposal1.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    // Proposal for a second sidechain
    Sidechain proposal2;
    proposal2.nSidechain = 1;
    proposal2.nVersion = 0;
    proposal2.title = "sidechain2";
    proposal2.description = "test";
    proposal2.hashID1 = GetRandHash();
    proposal2.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    // Create transaction outputs with sidechain proposals

    CTxOut out1;
    out1.scriptPubKey = proposal1.GetProposalScript();
    out1.nValue = 50 * CENT;
    BOOST_CHECK(out1.scriptPubKey.IsSidechainProposalCommit());

    CTxOut out2;
    out2.scriptPubKey = proposal2.GetProposalScript();
    out2.nValue = 50 * CENT;
    BOOST_CHECK(out2.scriptPubKey.IsSidechainProposalCommit());

    // Add both proposals to blocks and get them into SCDB
    BOOST_CHECK(scdbTest.Update(0, GetRandHash(), scdbTest.GetHashBlockLastSeen(), std::vector<CTxOut>{out1}));
    BOOST_CHECK(scdbTest.Update(1, GetRandHash(), scdbTest.GetHashBlockLastSeen(), std::vector<CTxOut>{out2}));

    // Check that the proposals were added
    std::vector<SidechainActivationStatus> vActivation;
    vActivation = scdbTest.GetSidechainActivationStatus();
    BOOST_CHECK(vActivation.size() == 2);
    BOOST_CHECK(vActivation.size() && vActivation.front().proposal.GetProposalScript() == proposal1.GetProposalScript());
    BOOST_CHECK(vActivation.size() && vActivation.back().proposal.GetProposalScript() == proposal2.GetProposalScript());

    // Start ACKing the proposals

    CBlock block;
    CMutableTransaction mtx;
    mtx.vin.resize(1);
    mtx.vin[0].prevout.SetNull();
    block.vtx.push_back(MakeTransactionRef(std::move(mtx)));
    GenerateSidechainActivationCommitment(block, vActivation.front().proposal.GetHash());
    GenerateSidechainActivationCommitment(block, vActivation.back().proposal.GetHash());

    // Add votes until the sidechains are activated
    int nHeight = 2;
    for (int i = 0; i < SIDECHAIN_ACTIVATION_PERIOD - 1; i++) {
        if (i == SIDECHAIN_ACTIVATION_PERIOD - 2) {
            // For the last block we only want to vote on the second proposal
            // as the first has already activated in the previous block
            CMutableTransaction mtxFinal = CMutableTransaction(*block.vtx[0]);
            mtxFinal.vout.clear();
            block.vtx[0] = MakeTransactionRef(std::move(mtx));
            GenerateSidechainActivationCommitment(block, vActivation.back().proposal.GetHash());
        }
        BOOST_CHECK(scdbTest.Update(nHeight, GetRandHash(), scdbTest.GetHashBlockLastSeen(), block.vtx.front()->vout));
        nHeight++;
    }

    // Proposals should be removed now
    vActivation = scdbTest.GetSidechainActivationStatus();
    BOOST_CHECK(vActivation.empty());

    // Both sidechains should be active now
    std::vector<Sidechain> vSidechain = scdbTest.GetActiveSidechains();
    BOOST_CHECK(vSidechain.size() == 2);
}

BOOST_AUTO_TEST_CASE(per_block_activation_limit_fail)
{
    // Test that only one sidechain activation commit is allowed for each
    // sidechain number per block. In this test we will ACK two sidechains that
    // have the same sidechain numbers, which should be rejected.
    SidechainDB scdbTest;

    Sidechain proposal1;
    proposal1.nSidechain = 0;
    proposal1.nVersion = 0;
    proposal1.title = "sidechain1";
    proposal1.description = "description";
    proposal1.hashID1 = uint256S("b55d224f1fda033d930c92b1b40871f209387355557dd5e0d2b5dd9bb813c33f");
    proposal1.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    // Proposal for a second sidechain
    Sidechain proposal2;
    proposal2.nSidechain = 0;
    proposal2.nVersion = 0;
    proposal2.title = "sidechain2";
    proposal2.description = "test";
    proposal2.hashID1 = GetRandHash();
    proposal2.hashID2 = uint160S("31d98584f3c570961359c308619f5cf2e9178482");

    // Create transaction outputs with sidechain proposals

    CTxOut out1;
    out1.scriptPubKey = proposal1.GetProposalScript();
    out1.nValue = 50 * CENT;
    BOOST_CHECK(out1.scriptPubKey.IsSidechainProposalCommit());

    CTxOut out2;
    out2.scriptPubKey = proposal2.GetProposalScript();
    out2.nValue = 50 * CENT;
    BOOST_CHECK(out2.scriptPubKey.IsSidechainProposalCommit());

    // Add both proposals to blocks and get them into SCDB
    BOOST_CHECK(scdbTest.Update(0, GetRandHash(), scdbTest.GetHashBlockLastSeen(), std::vector<CTxOut>{out1}));
    BOOST_CHECK(scdbTest.Update(1, GetRandHash(), scdbTest.GetHashBlockLastSeen(), std::vector<CTxOut>{out2}));

    // Check that the proposals were added
    std::vector<SidechainActivationStatus> vActivation;
    vActivation = scdbTest.GetSidechainActivationStatus();
    BOOST_CHECK(vActivation.size() == 2);
    BOOST_CHECK(vActivation.size() && vActivation.front().proposal.GetProposalScript() == proposal1.GetProposalScript());
    BOOST_CHECK(vActivation.size() && vActivation.back().proposal.GetProposalScript() == proposal2.GetProposalScript());

    // Start ACKing the proposals

    CBlock block;
    CMutableTransaction mtx;
    mtx.vin.resize(1);
    mtx.vin[0].prevout.SetNull();
    block.vtx.push_back(MakeTransactionRef(std::move(mtx)));
    GenerateSidechainActivationCommitment(block, proposal1.GetHash());
    GenerateSidechainActivationCommitment(block, proposal2.GetHash());

    // Acking two sidechains in one block with the same sidechain number should
    // fail
    BOOST_CHECK(!scdbTest.Update(2, GetRandHash(), scdbTest.GetHashBlockLastSeen(), block.vtx.front()->vout));
}

BOOST_AUTO_TEST_SUITE_END()

ext: cpp

max_stars_repo_name: drivechain-project/Mainchain

max_issues_repo_name: drivechain-project/Mainchain

max_forks_repo_name: drivechain-project/Mainchain


text:
#include "server/httpserv.h"
#include "../../../Simple-Web-Server/server_http.hpp"
#include "../../../Simple-Web-Server/client_http.hpp"
#include "../../../json/src/json.hpp"
#include "config.h"
#include "entity_manager.h"

// for convenience
using json = nlohmann::json;
//Added for the json-example
#define BOOST_SPIRIT_THREADSAFE
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>

//Added for the default_resource example
#include <fstream>
#include <boost/filesystem.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/array.hpp>

#include <vector>
#include <iostream>
#include <algorithm>
#ifdef HAVE_OPENSSL
#include "crypto.hpp"
#endif

using namespace std;
//Added for the json-example:
using namespace boost::property_tree;
namespace fs = boost::filesystem;
using namespace boost::lambda;
typedef SimpleWeb::Server<SimpleWeb::HTTP> HttpServer;
typedef SimpleWeb::Client<SimpleWeb::HTTP> HttpClient;

class Exception: public std::exception
{
public:
    /** Constructor (C strings).
     *  @param message C-style string error message.
     *                 The string contents are copied upon construction.
     *                 Hence, responsibility for deleting the char* lies
     *                 with the caller.
     */
    explicit Exception(const char* message):
      msg_(message)
      {
      }

    /** Constructor (C++ STL strings).
     *  @param message The error message.
     */
    explicit Exception(const std::string& message):
      msg_(message)
      {}

    /** Destructor.
     * Virtual to allow for subclassing.
     */
    virtual ~Exception() throw (){}

    /** Returns a pointer to the (constant) error description.
     *  @return A pointer to a const char*. The underlying memory
     *          is in posession of the Exception object. Callers must
     *          not attempt to free the memory.
     */
    virtual const char* what() const throw (){
       return msg_.c_str();
    }

protected:
    /** Error message.
     */
    std::string msg_;
};

//Added for the default_resource example
void default_resource_send(const HttpServer &server, const shared_ptr<HttpServer::Response> &response,
                           const shared_ptr<ifstream> &ifs);
	struct element
	{
		std::string filename;
		bool is_directory;
		std::vector<element> my_children;
	};

    string unescape(const string& s)
    {
      string res;
      string::const_iterator it = s.begin();
      while (it != s.end())
      {
        char c = *it++;
        if (c == '\\' && it != s.end())
        {
          switch (*it++) {
          case '\\': c = '\\'; break;
          case 'n': c = '\n'; break;
          case 't': c = '\t'; break;
          // all other escapes
          default:
            // invalid escape sequence - skip it. alternatively you can copy it as is, throw an exception...
            continue;
          }
        }
        res += c;
      }

      return res;
    }

//[{"text" : "Root", "id" : "1", "children" : true}]

    std::string SplitFilename (const string& str)
    {
      size_t found;
      //cout << "Splitting: " << str << endl;
      found=str.find_last_of("/\\");
      //cout << " folder: " << str.substr(0,found) << endl;
      //cout << " file: " << str.substr(found+1) << endl;
      return str.substr(found+1);
    }
    std::string SplitDirectoryname(const string& str)
    {
      size_t found;
      //cout << "Splitting: " << str << endl;
      found=str.find_last_of("/\\");
      //cout << " folder: " << str.substr(0,found) << endl;
      //cout << " file: " << str.substr(found+1) << endl;
      return str.substr(0, found);
    }

	bool load_directory_map( const fs::path & dir_path, json& j )
	{
        if ( !exists( dir_path ) ) return false;
        if(!is_directory(dir_path) )
        {
            return false;
        }
        std::string tmp_path = dir_path.string();
        if( tmp_path[tmp_path.size()-1] == '/' )
            tmp_path.erase(tmp_path.size()-1);

        //std::string dname = dir_path.parent_path().string();
        tmp_path = SplitFilename(tmp_path);
        j["text"] = tmp_path;//itr->path().string();
        j["type"] = "directory";
        j["data"]["relativePath"] = std::regex_replace(dir_path.string(), std::regex("\\" + std::string(DEFAULT_GAME_DATA_PATH)), "");
        j["data"]["systemPath"] = dir_path.string();
       // cout << dir_path.string() << endl;
        //cout << tmp_path.c_str() << endl;
        std::size_t str_hash = std::hash<std::string>{}(dir_path.string());

        j["id"] = std::to_string( str_hash );

        int cnt = std::count_if( fs::directory_iterator(dir_path), fs::directory_iterator(), static_cast<bool(*)(const fs::path&)>(fs::is_regular_file) );

        if( cnt > 0 )
        {
             //j["children"] = true;
             j["children"] = { };
        }
        else
        {
            j["children"] = false;
            return true;
        }

	  fs::directory_iterator end_itr; // default construction yields past-the-end
	  for ( fs::directory_iterator itr( dir_path );
		itr != end_itr;
		++itr )
	  {
	    if ( is_directory(itr->status()) )
	    {
            // if its a directory child..
            json new_child;
            load_directory_map( itr->path(), new_child);
            j["children"].push_back(new_child);

	    }
	    else //if ( itr->leaf() == file_name ) // see below
	    {
            json new_child;
            new_child["children"] = false;
            std::size_t str_hash2 = std::hash<std::string>{}(itr->path().string());

            new_child["id"] = std::to_string( str_hash2 );
            new_child["text"] = itr->path().filename().string();//
            new_child["data"]["relativePath"] = std::regex_replace(itr->path().string(), std::regex("\\" + std::string(DEFAULT_GAME_DATA_PATH)), "");
            new_child["data"]["systemPath"] = itr->path().string();
            new_child["icon"] = "jstree-file";
            new_child["type"] = "file";
           // load_directory_map( itr->path(), new_child);
            j["children"].push_back(new_child);

		//element e;
		//e.is_directory = false;
		//e.filename = itr->filename();
		//fs_map.push_back(e);
	      //path_found = itr->path();
	     // return false;
	    }
	  }
	  return false;
	}

int start_serv(int port) {

    HttpServer server;
    //HTTP-server at port 8080 using 1 thread
    //Unless you do more heavy non-threaded processing in the resources,
    //1 thread is usually faster than several threads

    server.config.port=port;

    //Add resources using path-regex and method-string, and an anonymous function
    //POST-example for the path /string, responds the posted string
    server.resource["^/string$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
        //Retrieve string:
        auto content=request->content.string();
        //request->content.string() is a convenience function for:
        //stringstream ss;
        //ss << request->content.rdbuf();
        //string content=ss.str();

        *response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n" << content;
    };

    //POST-example for the path /json, responds firstName+" "+lastName from the posted json
    //Responds with an appropriate error message if the posted json is not valid, or if firstName or lastName is missing
    //Example posted json:
    //{
    //  "firstName": "John",
    //  "lastName": "Smith",
    //  "age": 25
    //}
    server.resource["^/json$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
        try {
            ptree pt;
            read_json(request->content, pt);

            string name=pt.get<string>("firstName")+" "+pt.get<string>("lastName");

            *response << "HTTP/1.1 200 OK\r\n"
                      << "Content-Type: application/json\r\n"
                      << "Content-Length: " << name.length() << "\r\n\r\n"
                      << name;
        }
        catch(exception& e) {
            *response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" << e.what();
        }
    };

    server.resource["^/authenticate$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
        try {
            json j;
            j << request->content;
           // cout << j["relativePath"];
            json resp;
            std::size_t str_hash = std::hash<std::string>{}("fake");
            resp["sessionKey"] = std::to_string( str_hash );

            std::string jsonstr = resp.dump();

           /// std::string path_to_save = DEFAULT_GAME_DATA_PATH;
           // path_to_save += j["relativePath"];
                *response << "HTTP/1.1 200 OK\r\n"
                      << "Content-Type: application/json\r\n"
                      << "Access-Control-Allow-Origin: *\r\n"
                      << "Content-Length: " << jsonstr.length() << "\r\n\r\n" << jsonstr;
        }
        catch(exception& e) {
            *response << "HTTP/1.1 400 Bad Request\r\nAccess-Control-Allow-Origin: *\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" << e.what();
        }
    };

    server.resource["^/authenticate$"]["OPTIONS"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
        try {

            *response << "HTTP/1.1 200 OK\r\n"
                      << "Date: Mon, 01 Dec 2008 01:15:39 GMT\r\n"
                      << "Access-Control-Allow-Origin: *\r\n"
                      << "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n"
                      << "Access-Control-Allow-Headers: Content-Type\r\n"
                      << "Accept-Encoding: *\r\n"
                      << "Accept-Language: *\r\n"
                      << "Accept\r\n"
                      << "Cache-Control:public, max-age=31536000\r\n"
                      << "Access-Control-Max-Age: 31536000\r\n"
                      << "Content-Length: 0\r\n";

        }
        catch(exception& e) {
            *response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" << e.what();
        }
    };

    //POST-example for the path /save, responds firstName+" "+lastName from the posted json
    //Responds with an appropriate error message if the posted json is not valid, or if firstName or lastName is missing
    //Example posted json:
    //{
    //  "firstName": "John",
    //  "lastName": "Smith",
    //  "age": 25
    //}
    server.resource["^/save$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
        try {
            json j;
            j << request->content;
           // cout << j["relativePath"];

            std::string path_to_save = DEFAULT_GAME_DATA_PATH;
            path_to_save += j["relativePath"];

            if( j["create"] && fs::exists(fs::path(path_to_save) ))
            {
                std::string relpath = j["relativePath"];
                std::string ferror = "Error. File " + relpath + " already exists.";
                *response << "HTTP/1.1 400 Bad Request\r\n"
                 << "Access-Control-Allow-Origin: *\r\n"
                 << "Content-Length: " << ferror.length() << "\r\n\r\n" << ferror;
                return;
            }
            /*
            if( j["isDirectory"] )
            {
                json resp;
                resp["relativePath"] = j["relativePath"];
                std::size_t str_hash = std::hash<std::string>{}(path_to_save);

                resp["id"] = std::to_string( str_hash );
                std::string jsonstr = resp.dump();

                boost::filesystem::path dir(path_to_save);
                if (boost::filesystem::create_directory(dir))
                {

                    //std::cout<<"wrote the file successfully!"<<std::endl;
                    *response << "HTTP/1.1 200 OK\r\n"
                          << "Content-Type: application/json\r\n"
                          << "Access-Control-Allow-Origin: *\r\n"
                          << "Content-Length: " << jsonstr.length() << "\r\n\r\n" << jsonstr;
                }
                else
                {
                    //std::cerr<<"Failed to open file : "<<SYSERROR()<<std::endl;
                    //return -1;
                    std::string ferror = "Unable to save file " + path_to_save;
                    *response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << ferror.length() << "\r\n\r\n" << ferror;
                }
                return;
            }
             */

            cout << "Saving file: " << path_to_save << endl;

           // std::string l = j.dump(4);
            //cout << l << endl;

            //std::string input;
            //std::cin >> input;
            std::string source = unescape(j["source"]);
            cout << source << endl;
            std::ofstream out(path_to_save);

            json resp;
            resp["relativePath"] = j["relativePath"];
            std::size_t str_hash = std::hash<std::string>{}(path_to_save);

            resp["id"] = std::to_string( str_hash );
            std::string jsonstr = resp.dump();
            if(out.is_open())
            {
                out << source;
                out.flush();
                out.close();
                //std::cout<<"wrote the file successfully!"<<std::endl;
                *response << "HTTP/1.1 200 OK\r\n"
                      << "Content-Type: application/json\r\n"
                      << "Access-Control-Allow-Origin: *\r\n"
                      << "Content-Length: " << jsonstr.length() << "\r\n\r\n" << jsonstr;
            }
            else
            {
                //std::cerr<<"Failed to open file : "<<SYSERROR()<<std::endl;
                //return -1;
                std::string ferror = "Unable to save file " + path_to_save;
                *response << "HTTP/1.1 400 Bad Request\r\n"
                << "Access-Control-Allow-Origin: *\r\n"
                << "Content-Length: " << ferror.length() << "\r\n\r\n" << ferror;
            }

           // ptree pt;
           // read_json(request->content, pt);

           // string name=pt.get<string>("firstName")+" "+pt.get<string>("lastName");

           // *response << "HTTP/1.1 200 OK\r\n"
           //           << "Content-Type: application/json\r\n"
            ////          << "Access-Control-Allow-Origin: *\r\n"
           //           << "Content-Length: 0" << "\r\n\r\n";
        }
        catch(exception& e) {
            *response << "HTTP/1.1 400 Bad Request\r\nAccess-Control-Allow-Origin: *\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" << e.what();
        }
    };

    server.resource["^/save$"]["OPTIONS"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
        try {

            *response << "HTTP/1.1 200 OK\r\n"
                      << "Date: Mon, 01 Dec 2008 01:15:39 GMT\r\n"
                      << "Access-Control-Allow-Origin: *\r\n"
                      << "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n"
                      << "Access-Control-Allow-Headers: Content-Type\r\n"
                      << "Accept-Encoding: *\r\n"
                      << "Accept-Language: *\r\n"
                      << "Accept\r\n"
                      << "Cache-Control:public, max-age=31536000\r\n"
                      << "Access-Control-Max-Age: 31536000\r\n"
                      << "Content-Length: 0\r\n";

        }
        catch(exception& e) {
            *response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" << e.what();
        }
    };

    server.resource["^/fs$"]["OPTIONS"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
        try {

            *response << "HTTP/1.1 200 OK\r\n"
                      << "Date: Mon, 01 Dec 2008 01:15:39 GMT\r\n"
                      << "Access-Control-Allow-Origin: *\r\n"
                      << "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n"
                      << "Access-Control-Allow-Headers: Content-Type\r\n"
                      << "Accept-Encoding: *\r\n"
                      << "Accept-Language: *\r\n"
                      << "Accept\r\n"
                      << "Cache-Control:public, max-age=31536000\r\n"
                      << "Access-Control-Max-Age: 31536000\r\n"
                      << "Content-Length: 0\r\n";

        }
        catch(exception& e) {
            *response << "HTTP/1.1 400 Bad Request\r\nAccess-Control-Allow-Origin: *\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" << e.what();
        }
    };

    //GET-example for the path /info
    //Responds with request-information
    server.resource["^/info$"]["GET"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
        stringstream content_stream;
        content_stream << "
Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")
";
        content_stream << request->method << " " << request->path << " HTTP/" << request->http_version << "
";
        for(auto& header: request->header) {
            content_stream << header.first << ": " << header.second << "
";
        }

        //find length of content_stream (length received using content_stream.tellp())
        content_stream.seekp(0, ios::end);

        *response <<  "HTTP/1.1 200 OK\r\nContent-Length: " << content_stream.tellp() << "\r\n\r\n" << content_stream.rdbuf();
    };

    server.resource["^/fs$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {

        try
        {
            json j;
            j << request->content;

            if( j["op"] == "mkdir")
            {
                std::string game_path = DEFAULT_GAME_DATA_PATH;

                //std::string relpath = j["relativePath"];

                //relpath = SplitDirectoryname(relpath);

                //game_path += relpath;

                std::string fileA = j["fileA"];
                fileA = game_path  + fileA;

                if( fs::exists(fileA) )
                {
                    throw Exception( "File already exists.");
                }
                fs::create_directory( fs::path(fileA) );//rename( fs::path(fileA), fs::path(fileB) );

                json resp;
                resp["relativePath"] =  std::regex_replace(fileA, std::regex("\\" + std::string(DEFAULT_GAME_DATA_PATH)), "");//j["relativePath"];
                std::size_t str_hash = std::hash<std::string>{}(fileA);

                resp["id"] = std::to_string( str_hash );
                std::string jsonstr = resp.dump();

                *response <<  "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nContent-Length: " << jsonstr.length() << "\r\n\r\n" << jsonstr;
            }
            else if( j["op"] == "rm")
            {
                std::string game_path = DEFAULT_GAME_DATA_PATH;

                std::string relpath = j["relativePath"];

                game_path += relpath;

                if( !fs::exists(game_path) )
                {
                    throw Exception( "File does not exist.");
                }
                fs::remove( fs::path(game_path) );//rename( fs::path(fileA), fs::path(fileB) );

                json resp;
                resp["relativePath"] =  std::regex_replace(game_path, std::regex("\\" + std::string(DEFAULT_GAME_DATA_PATH)), "");//j["relativePath"];
                std::size_t str_hash = std::hash<std::string>{}(game_path);

                resp["id"] = std::to_string( str_hash );
                std::string jsonstr = resp.dump();

                *response <<  "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nContent-Length: " << jsonstr.length() << "\r\n\r\n" << jsonstr;
            }
            else if( j["op"] == "rename")
            {
                std::string game_path = DEFAULT_GAME_DATA_PATH;

                std::string relpath = j["relativePath"];

                relpath = SplitDirectoryname(relpath);
                game_path += relpath;
                std::string fileA = j["fileA"];
                fileA = game_path  + "/" + fileA;
                std::string fileB = j["fileB"];
                fileB = game_path  + "/" + fileB;
               // fs::path fileA = fs::path(path_to_save + "/" + "")

                if( !fs::exists(fileA) )
                {
                    throw Exception( "File does not exist.");
                }
                if( fs::exists(fileB) )
                {
                    throw Exception("File already exists with that name.");
                }
                fs::rename( fs::path(fileA), fs::path(fileB) );

                json resp;
                resp["relativePath"] =  std::regex_replace(fileB, std::regex("\\" + std::string(DEFAULT_GAME_DATA_PATH)), "");//j["relativePath"];
                std::size_t str_hash = std::hash<std::string>{}(fileB);

                resp["id"] = std::to_string( str_hash );
                std::string jsonstr = resp.dump();

                *response <<  "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nContent-Length: " << jsonstr.length() << "\r\n\r\n" << jsonstr;
               // fs::rename()

            }
            else if( j["op"] == "compile")
            {
                std::string game_path = DEFAULT_GAME_DATA_PATH;
                std::string fileA = j["fileA"];
                fileA = game_path + fileA;

                if( !fs::exists(fileA) )
                {
                    throw Exception( "File does not exist.");
                }

                //fs::rename( fs::path(fileA), fs::path(fileB) );
                std::string reason;
                if( entity_manager::Instance().compile_entity(fileA, reason) )
                {
                    json resp;
                    //std::size_t str_hash = std::hash<std::string>{}(fileA);

                    resp["result"] = "OK";
                    resp["text"] = "Compiled " + std::regex_replace(fileA, std::regex("\\" + std::string(DEFAULT_GAME_DATA_PATH)), "");
                    std::string jsonstr = resp.dump();

                    *response <<  "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nContent-Length: " << jsonstr.length() << "\r\n\r\n" << jsonstr;
                }
                else
                {
                    throw Exception(reason);
                }

               // fs::rename()

            }
            else if( j["op"] == "move")
            {
                std::string game_path = DEFAULT_GAME_DATA_PATH;
                std::string fileA = j["fileA"];
                fileA = game_path  + fileA;
                std::string fileB = j["fileB"];
                fileB = game_path  + fileB;

                std::string new_file_path = fileB + "/" + SplitFilename(fileA);
               // fs::path fileA = fs::path(path_to_save + "/" + "")

                if( !fs::exists(fileA) )
                {
                    throw Exception( "File does not exist.");
                }
                if( fs::exists(new_file_path) )
                {
                    throw Exception("File already exists with that name.");
                }
                fs::rename( fs::path(fileA), fs::path(new_file_path) );

                json resp;
                resp["relativePath"] =  std::regex_replace(new_file_path, std::regex("\\" + std::string(DEFAULT_GAME_DATA_PATH)), "");//j["relativePath"];
                std::size_t str_hash = std::hash<std::string>{}(new_file_path);

                resp["id"] = std::to_string( str_hash );
                std::string jsonstr = resp.dump();

                *response <<  "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nContent-Length: " << jsonstr.length() << "\r\n\r\n" << jsonstr;
               // fs::rename()

            }
            else
            {
                throw Exception("Operation not permitted.");
            }
        }
        catch(const exception &e) {

            *response << "HTTP/1.1 400 Bad Request\r\nAccess-Control-Allow-Origin: *\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" << e.what();
        }

    };

    //GET-example for the path /match/[number], responds with the matched string in path (number)
    //For instance a request GET /match/123 will receive: 123
    server.resource["^/list/([0-9]+)$"]["GET"]=[&server](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {

    json j1;
    load_directory_map(DEFAULT_GAME_DATA_PATH, j1);
    std::string test = j1.dump(4);
    //cout << s << std::endl;
    //cout << "Servicing request.." << endl;
   // cout << test;
        *response << "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\n"
        << "Content-Length: " << test.length() << "\r\n\r\n" << test;
    };

    //Get example simulating heavy work in a separate thread
    server.resource["^/work$"]["GET"]=[&server](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
        thread work_thread([response] {
            this_thread::sleep_for(chrono::seconds(0));
            string message="Work done";
            *response << "HTTP/1.1 200 OK\r\nContent-Length: " << message.length() << "\r\n\r\n" << message;
        });
        work_thread.detach();
    };

/*
    server.resource["/contents"]["GET"]=[&server](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
        try {
            auto web_root_path=boost::filesystem::canonical(DEFAULT_GAME_DATA_PATH);
            auto path=boost::filesystem::canonical(web_root_path/request->path);
            //Check if path is within web_root_path
            if(distance(web_root_path.begin(), web_root_path.end())>distance(path.begin(), path.end()) ||
               !equal(web_root_path.begin(), web_root_path.end(), path.begin()))
                throw invalid_argument("path must be within root path");
            if(!boost::filesystem::is_directory(path))
                throw invalid_argument("must be a file");
            if(!(boost::filesystem::exists(path) && boost::filesystem::is_regular_file(path)))
                throw invalid_argument("file does not exist");

            std::string cache_control, etag;

            // Uncomment the following line to enable Cache-Control
             //cache_control="Cache-Control: max-age=86400\r\n";

            auto ifs=make_shared<ifstream>();
            ifs->open(path.string(), ifstream::in | ios::binary | ios::ate);
            cout << path.string() <<  endl;
            if(*ifs) {
                auto length=ifs->tellg();
                ifs->seekg(0, ios::beg);

                *response << "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\n" << cache_control << etag << "Content-Length: " << length << "\r\n\r\n";
                default_resource_send(server, response, ifs);
            }
            else
                throw invalid_argument("could not read file");
        }
        catch(const exception &e) {
            string content="Could not open path "+request->path+": "+e.what();
            *response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << content.length() << "\r\n\r\n" << content;
        }
    };
     *
     */

    //Default GET-example. If no other matches, this anonymous function will be called.
    //Will respond with content in the web/-directory, and its subdirectories.
    //Default file: index.html
    //Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server
    server.default_resource["GET"]=[&server](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
        try {
            //cout << "Servicing request.." << endl;
            auto web_root_path=boost::filesystem::canonical(DEFAULT_GAME_DATA_PATH);
            auto path=boost::filesystem::canonical(web_root_path/request->path);
            //Check if path is within web_root_path
            if(distance(web_root_path.begin(), web_root_path.end())>distance(path.begin(), path.end()) ||
               !equal(web_root_path.begin(), web_root_path.end(), path.begin()))
                throw invalid_argument("path must be within root path");
            if(boost::filesystem::is_directory(path))
                throw invalid_argument("must be a file");
            if(!(boost::filesystem::exists(path) && boost::filesystem::is_regular_file(path)))
                throw invalid_argument("file does not exist");

            std::string cache_control, etag;

            // Uncomment the following line to enable Cache-Control
            // cache_control="Cache-Control: max-age=86400\r\n";

#ifdef HAVE_OPENSSL
            // Uncomment the following lines to enable ETag
            // {
            //     ifstream ifs(path.string(), ifstream::in | ios::binary);
            //     if(ifs) {
            //         auto hash=SimpleWeb::Crypto::to_hex_string(SimpleWeb::Crypto::md5(ifs));
            //         etag = "ETag: \""+hash+"\"\r\n";
            //         auto it=request->header.find("If-None-Match");
            //         if(it!=request->header.end()) {
            //             if(!it->second.empty() && it->second.compare(1, hash.size(), hash)==0) {
            //                 *response << "HTTP/1.1 304 Not Modified\r\n" << cache_control << etag << "\r\n\r\n";
            //                 return;
            //             }
            //         }
            //     }
            //     else
            //         throw invalid_argument("could not read file");
            // }
#endif

            auto ifs=make_shared<ifstream>();
            ifs->open(path.string());//, ifstream::in | ios::binary | ios::ate);
            ///std::ifstream t("file.txt");

           // cout << path.string() << endl;
            if(*ifs) {
                std::stringstream buffer;
                buffer << (*ifs).rdbuf();
                json jresp;
                jresp["text"] = buffer.str();
                jresp["path"] = path.string();
               // auto length=ifs->tellg();
                //ifs->seekg(0, ios::beg);
                std::string test = jresp.dump(4);
                //cout << test << std::endl;
                //cout << "Sending response.." << endl;
                *response << "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nContent-Length: " << test.length() << "\r\n\r\n" << test;
                //};
               // *response << "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\n" << cache_control << etag << "Content-Length: " << length << "\r\n\r\n";
                default_resource_send(server, response, ifs);
               // cout << "Done." << endl;
            }
            else
                throw invalid_argument("could not read file");
        }
        catch(const exception &e) {
            string content="Could not open path "+request->path+": "+e.what();
            *response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << content.length() << "\r\n\r\n" << content;
        }
    };

    thread server_thread([&server](){
        //Start server
        server.start();
    });
    /*
    //Wait for server to start so that the client can connect
    this_thread::sleep_for(chrono::seconds(1));

    //Client examples
    HttpClient client("localhost:8090");
    auto r1=client.request("GET", "/match/123");
    cout << r1->content.rdbuf() << endl;

    string json_string="{\"firstName\": \"John\",\"lastName\": \"Smith\",\"age\": 25}";
    auto r2=client.request("POST", "/string", json_string);
    cout << r2->content.rdbuf() << endl;

    auto r3=client.request("POST", "/json", json_string);
    cout << r3->content.rdbuf() << endl;
     */

    server_thread.join();

    return 0;
}

void default_resource_send(const HttpServer &server, const shared_ptr<HttpServer::Response> &response,
                           const shared_ptr<ifstream> &ifs) {
    //read and send 128 KB at a time
    static vector<char> buffer(131072); // Safe when server is running on one thread
    streamsize read_length;
    if((read_length=ifs->read(&buffer[0], buffer.size()).gcount())>0) {
        response->write(&buffer[0], read_length);
        if(read_length==static_cast<streamsize>(buffer.size())) {
            server.send(response, [&server, response, ifs](const boost::system::error_code &ec) {
                if(!ec)
                    default_resource_send(server, response, ifs);
                else
                    cerr << "Connection interrupted" << endl;
            });
        }
    }
}

max_stars_repo_name: CalielOfSeptem/septem

max_issues_repo_name: HoraceWeebler/septem

max_forks_repo_name: HoraceWeebler/septem