<commit_before>// $Id: petsc_nonlinear_solver.C,v 1.7 2005-02-02 20:51:17 benkirk Exp $

// The libMesh Finite Element Library.
// Copyright (C) 2002-2004  Benjamin S. Kirk, John W. Peterson

// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#include "libmesh_common.h"

#ifdef HAVE_PETSC

// C++ includes

// Local Includes
#include "petsc_nonlinear_solver.h"
#include "petsc_vector.h"
#include "petsc_matrix.h"

//--------------------------------------------------------------------
// Functions with C linkage to pass to PETSc.  PETSc will call these
// methods as needed.
//
// Since they must have C linkage they have no knowledge of a namespace.
// Give them an obscure name to avoid namespace pollution.
extern "C"
{
  // Older versions of PETSc do not have the different int typedefs.
  // On 64-bit machines, PetscInt may actually be a long long int.
  // This change occurred in Petsc-2.2.1.
# if (((PETSC_VERSION_MAJOR == 2) && (PETSC_VERSION_MINOR == 2) && (PETSC_VERSION_SUBMINOR == 0)) || \
      ((PETSC_VERSION_MAJOR == 2) && (PETSC_VERSION_MINOR <= 1)))
  typedef int PetscErrorCode;
  typedef int PetscInt;
#endif

  //-------------------------------------------------------------------
  // this function is called by PETSc at the end of each nonlinear step
  PetscErrorCode
  __libmesh_petsc_snes_monitor (SNES, PetscInt its, PetscReal fnorm, void *)
  {
    //int ierr=0;

    std::cout << "  NL conv: step " << its
	      << std::scientific
	      //<< ", |u|_oo = "      << 0
	      << ", |resid|_2 = "   << fnorm
              << std::endl;

    //return ierr;
    return 0;
  }

  //---------------------------------------------------------------
  // this function is called by PETSc to evaluate the residual at X
  PetscErrorCode
  __libmesh_petsc_snes_residual (SNES, Vec x, Vec r, void *ctx)
  {
    int ierr=0;

    assert (x   != NULL);
    assert (r   != NULL);
    assert (ctx != NULL);

    PetscNonlinearSolver<Number>* solver =
      static_cast<PetscNonlinearSolver<Number>*> (ctx);

    PetscVector<Number> X_global(x), R(r);
    PetscVector<Number> X_local(X_global.size());

    X_global.localize (X_local);

    if (solver->residual != NULL) solver->residual (X_local, R);
    if (solver->matvec   != NULL) solver->matvec   (X_local, &R, NULL);

    R.close();

//     std::cout << "X.size()=" << X_global.size()
// 	      << ", R.size()=" << R.size()
// 	      << std::endl;

    return ierr;
  }

  //---------------------------------------------------------------
  // this function is called by PETSc to evaluate the Jacobian at X
  PetscErrorCode
  __libmesh_petsc_snes_jacobian (SNES, Vec x, Mat *jac, Mat *pc, MatStructure *msflag, void *ctx)
  {
    int ierr=0;

    assert (ctx != NULL);

    PetscNonlinearSolver<Number>* solver =
      static_cast<PetscNonlinearSolver<Number>*> (ctx);

    PetscMatrix<Number> PC(*pc);
    PetscMatrix<Number> Jac(*jac);
    PetscVector<Number> X_global(x);
    PetscVector<Number> X_local (X_global.size());

    X_global.localize (X_local);

    if (solver->jacobian != NULL) solver->jacobian (X_local, PC);
    if (solver->matvec   != NULL) solver->matvec   (X_local, NULL, &PC);

    PC.close();
    Jac.close();

    *msflag = SAME_NONZERO_PATTERN;

//     here();

//     std::cout << "X.size()=" << X_global.size()
// 	      << std::endl;

    return ierr;
  }

} // end extern "C"
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// PetscNonlinearSolver<> methods
template <typename T>
void PetscNonlinearSolver<T>::clear ()
{
  if (this->initialized())
    {
      this->_is_initialized = false;

      int ierr=0;

      ierr = SNESDestroy(_snes);
             CHKERRABORT(PETSC_COMM_WORLD,ierr);
    }
}

template <typename T>
void PetscNonlinearSolver<T>::init ()
{
  // Initialize the data structures if not done so already.
  if (!this->initialized())
    {
      this->_is_initialized = true;

      int ierr=0;

      ierr = SNESCreate(PETSC_COMM_WORLD,&_snes);
             CHKERRABORT(PETSC_COMM_WORLD,ierr);

      ierr = SNESSetMonitor (_snes, __libmesh_petsc_snes_monitor,
			     this, PETSC_NULL);
             CHKERRABORT(PETSC_COMM_WORLD,ierr);

      ierr = SNESSetFromOptions(_snes);
             CHKERRABORT(PETSC_COMM_WORLD,ierr);
    }
}

template <typename T>
std::pair<unsigned int, Real>
PetscNonlinearSolver<T>::solve (SparseMatrix<T>&  jac_in,  // System Jacobian Matrix
				NumericVector<T>& x_in,    // Solution vector
				NumericVector<T>& r_in,    // Residual vector
				const double,              // Stopping tolerance
				const unsigned int)
{
  //this->init ();

//   std::cout << "x.size()=" << x_in.size()
// 	    << ", r.size()=" << r_in.size()
// 	    << std::endl;

  PetscMatrix<T>* jac = dynamic_cast<PetscMatrix<T>*>(&jac_in);
  PetscVector<T>* x   = dynamic_cast<PetscVector<T>*>(&x_in);
  PetscVector<T>* r   = dynamic_cast<PetscVector<T>*>(&r_in);

  // We cast to pointers so we can be sure that they succeeded
  // by comparing the result against NULL.
  assert(jac != NULL); assert(jac->mat() != NULL);
  assert(x   != NULL); assert(x->vec()   != NULL);
  assert(r   != NULL); assert(r->vec()   != NULL);

  int ierr=0;
  int n_iterations =0;

  //Mat A = jac->mat();

  ierr = SNESCreate(PETSC_COMM_WORLD,&_snes);
         CHKERRABORT(PETSC_COMM_WORLD,ierr);

  ierr = SNESSetMonitor (_snes, __libmesh_petsc_snes_monitor,
			 this, PETSC_NULL);
         CHKERRABORT(PETSC_COMM_WORLD,ierr);

  ierr = SNESSetFunction (_snes, r->vec(), __libmesh_petsc_snes_residual, this);
         CHKERRABORT(PETSC_COMM_WORLD,ierr);

  ierr = SNESSetJacobian (_snes, jac->mat(), jac->mat(), __libmesh_petsc_snes_jacobian, this);
         CHKERRABORT(PETSC_COMM_WORLD,ierr);

  ierr = SNESSetFromOptions(_snes);
         CHKERRABORT(PETSC_COMM_WORLD,ierr);

  // Older versions (at least up to 2.1.5) of SNESSolve took 3 arguments,
  // the last one being a pointer to an int to hold the number of iterations required.
# if (PETSC_VERSION_MAJOR == 2) && (PETSC_VERSION_MINOR <= 1)
 ierr = SNESSolve (_snes, x->vec(), &n_iterations);
        CHKERRABORT(PETSC_COMM_WORLD,ierr);
#else
 ierr = SNESSolve (_snes, x->vec());
        CHKERRABORT(PETSC_COMM_WORLD,ierr);
#endif

//   if (A != jac->mat())
//     {
//       ierr = MatDestroy(A);
//              CHKERRABORT(PETSC_COMM_WORLD,ierr);
//     }

 ierr = SNESDestroy(_snes);
        CHKERRABORT(PETSC_COMM_WORLD,ierr);

  // return the # of its. and the final residual norm.  Note that
  // n_iterations may be zero for PETSc versions 2.2.x and greater.
  return std::make_pair(n_iterations, 0.);
}

//------------------------------------------------------------------
// Explicit instantiations
template class PetscNonlinearSolver<Number>;

#endif // #ifdef HAVE_PETSC
<commit_msg>code cleanup<commit_after>// $Id: petsc_nonlinear_solver.C,v 1.8 2005-02-04 13:36:39 benkirk Exp $

// The libMesh Finite Element Library.
// Copyright (C) 2002-2004  Benjamin S. Kirk, John W. Peterson

// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#include "libmesh_common.h"

#ifdef HAVE_PETSC

// C++ includes

// Local Includes
#include "petsc_nonlinear_solver.h"
#include "petsc_vector.h"
#include "petsc_matrix.h"

//--------------------------------------------------------------------
// Functions with C linkage to pass to PETSc.  PETSc will call these
// methods as needed.
//
// Since they must have C linkage they have no knowledge of a namespace.
// Give them an obscure name to avoid namespace pollution.
extern "C"
{
  // Older versions of PETSc do not have the different int typedefs.
  // On 64-bit machines, PetscInt may actually be a long long int.
  // This change occurred in Petsc-2.2.1.
# if (((PETSC_VERSION_MAJOR == 2) && (PETSC_VERSION_MINOR == 2) && (PETSC_VERSION_SUBMINOR == 0)) || \
      ((PETSC_VERSION_MAJOR == 2) && (PETSC_VERSION_MINOR <= 1)))
  typedef int PetscErrorCode;
  typedef int PetscInt;
#endif

  //-------------------------------------------------------------------
  // this function is called by PETSc at the end of each nonlinear step
  PetscErrorCode
  __libmesh_petsc_snes_monitor (SNES, PetscInt its, PetscReal fnorm, void *)
  {
    //int ierr=0;

    if (its > 0)
      std::cout << "  NL step " << its
		<< std::scientific
		<< ", |residual|_2 = " << fnorm
		<< std::endl;

    //return ierr;
    return 0;
  }

  //---------------------------------------------------------------
  // this function is called by PETSc to evaluate the residual at X
  PetscErrorCode
  __libmesh_petsc_snes_residual (SNES, Vec x, Vec r, void *ctx)
  {
    int ierr=0;

    assert (x   != NULL);
    assert (r   != NULL);
    assert (ctx != NULL);

    PetscNonlinearSolver<Number>* solver =
      static_cast<PetscNonlinearSolver<Number>*> (ctx);

    PetscVector<Number> X_global(x), R(r);
    PetscVector<Number> X_local(X_global.size());

    X_global.localize (X_local);

    if (solver->residual != NULL) solver->residual (X_local, R);
    if (solver->matvec   != NULL) solver->matvec   (X_local, &R, NULL);

    R.close();

    return ierr;
  }

  //---------------------------------------------------------------
  // this function is called by PETSc to evaluate the Jacobian at X
  PetscErrorCode
  __libmesh_petsc_snes_jacobian (SNES, Vec x, Mat *jac, Mat *pc, MatStructure *msflag, void *ctx)
  {
    int ierr=0;

    assert (ctx != NULL);

    PetscNonlinearSolver<Number>* solver =
      static_cast<PetscNonlinearSolver<Number>*> (ctx);

    PetscMatrix<Number> PC(*pc);
    PetscMatrix<Number> Jac(*jac);
    PetscVector<Number> X_global(x);
    PetscVector<Number> X_local (X_global.size());

    X_global.localize (X_local);

    if (solver->jacobian != NULL) solver->jacobian (X_local, PC);
    if (solver->matvec   != NULL) solver->matvec   (X_local, NULL, &PC);

    PC.close();
    Jac.close();

    *msflag = SAME_NONZERO_PATTERN;

    return ierr;
  }

} // end extern "C"
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// PetscNonlinearSolver<> methods
template <typename T>
void PetscNonlinearSolver<T>::clear ()
{
  if (this->initialized())
    {
      this->_is_initialized = false;

      int ierr=0;

      ierr = SNESDestroy(_snes);
             CHKERRABORT(PETSC_COMM_WORLD,ierr);
    }
}

template <typename T>
void PetscNonlinearSolver<T>::init ()
{
  // Initialize the data structures if not done so already.
  if (!this->initialized())
    {
      this->_is_initialized = true;

      int ierr=0;

      ierr = SNESCreate(PETSC_COMM_WORLD,&_snes);
             CHKERRABORT(PETSC_COMM_WORLD,ierr);

      ierr = SNESSetMonitor (_snes, __libmesh_petsc_snes_monitor,
			     this, PETSC_NULL);
             CHKERRABORT(PETSC_COMM_WORLD,ierr);

      ierr = SNESSetFromOptions(_snes);
             CHKERRABORT(PETSC_COMM_WORLD,ierr);
    }
}

template <typename T>
std::pair<unsigned int, Real>
PetscNonlinearSolver<T>::solve (SparseMatrix<T>&  jac_in,  // System Jacobian Matrix
				NumericVector<T>& x_in,    // Solution vector
				NumericVector<T>& r_in,    // Residual vector
				const double,              // Stopping tolerance
				const unsigned int)
{
  //this->init ();

//   std::cout << "x.size()=" << x_in.size()
// 	    << ", r.size()=" << r_in.size()
// 	    << std::endl;

  PetscMatrix<T>* jac = dynamic_cast<PetscMatrix<T>*>(&jac_in);
  PetscVector<T>* x   = dynamic_cast<PetscVector<T>*>(&x_in);
  PetscVector<T>* r   = dynamic_cast<PetscVector<T>*>(&r_in);

  // We cast to pointers so we can be sure that they succeeded
  // by comparing the result against NULL.
  assert(jac != NULL); assert(jac->mat() != NULL);
  assert(x   != NULL); assert(x->vec()   != NULL);
  assert(r   != NULL); assert(r->vec()   != NULL);

  int ierr=0;
  int n_iterations =0;

  //Mat A = jac->mat();

  ierr = SNESCreate(PETSC_COMM_WORLD,&_snes);
         CHKERRABORT(PETSC_COMM_WORLD,ierr);

  ierr = SNESSetMonitor (_snes, __libmesh_petsc_snes_monitor,
			 this, PETSC_NULL);
         CHKERRABORT(PETSC_COMM_WORLD,ierr);

  ierr = SNESSetFunction (_snes, r->vec(), __libmesh_petsc_snes_residual, this);
         CHKERRABORT(PETSC_COMM_WORLD,ierr);

  ierr = SNESSetJacobian (_snes, jac->mat(), jac->mat(), __libmesh_petsc_snes_jacobian, this);
         CHKERRABORT(PETSC_COMM_WORLD,ierr);

  ierr = SNESSetFromOptions(_snes);
         CHKERRABORT(PETSC_COMM_WORLD,ierr);

  // Older versions (at least up to 2.1.5) of SNESSolve took 3 arguments,
  // the last one being a pointer to an int to hold the number of iterations required.
# if (PETSC_VERSION_MAJOR == 2) && (PETSC_VERSION_MINOR <= 1)
 ierr = SNESSolve (_snes, x->vec(), &n_iterations);
        CHKERRABORT(PETSC_COMM_WORLD,ierr);
#else
 ierr = SNESSolve (_snes, x->vec());
        CHKERRABORT(PETSC_COMM_WORLD,ierr);
#endif

//   if (A != jac->mat())
//     {
//       ierr = MatDestroy(A);
//              CHKERRABORT(PETSC_COMM_WORLD,ierr);
//     }

 ierr = SNESDestroy(_snes);
        CHKERRABORT(PETSC_COMM_WORLD,ierr);

  // return the # of its. and the final residual norm.  Note that
  // n_iterations may be zero for PETSc versions 2.2.x and greater.
  return std::make_pair(n_iterations, 0.);
}

//------------------------------------------------------------------
// Explicit instantiations
template class PetscNonlinearSolver<Number>;

#endif // #ifdef HAVE_PETSC
<|endoftext|>


<commit_before>////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Kaveh Vahedipour
////////////////////////////////////////////////////////////////////////////////

#include "FailedLeader.h"

#include "Agency/Agent.h"
#include "Agency/Job.h"
#include "Agency/JobContext.h"

#include <algorithm>
#include <vector>

using namespace arangodb::consensus;

FailedLeader::FailedLeader(Node const& snapshot, AgentInterface* agent,
                           std::string const& jobId, std::string const& creator,
                           std::string const& database,
                           std::string const& collection,
                           std::string const& shard, std::string const& from)
    : Job(NOTFOUND, snapshot, agent, jobId, creator), _database(database),
      _collection(collection), _shard(shard), _from(from) {}

FailedLeader::FailedLeader(
  Node const& snapshot, AgentInterface* agent, JOB_STATUS status,
  std::string const& jobId) : Job(status, snapshot, agent, jobId) {

  // Get job details from agency:
  try {
    std::string path = pos[status] + _jobId + "/";
    _database = _snapshot(path + "database").getString();
    _collection = _snapshot(path + "collection").getString();
    _from = _snapshot(path + "fromServer").getString();
    try {
      // set only if already started
      _to = _snapshot(path + "toServer").getString();
    } catch (...) {}
    _shard = _snapshot(path + "shard").getString();
    _creator = _snapshot(path + "creator").getString();
    _created = stringToTimepoint(_snapshot(path + "timeCreated").getString());
  } catch (std::exception const& e) {
    std::stringstream err;
    err << "Failed to find job " << _jobId << " in agency: " << e.what();
    LOG_TOPIC(ERR, Logger::SUPERVISION) << err.str();
    finish("", _shard, false, err.str());
    _status = FAILED;
  }

}

FailedLeader::~FailedLeader() {}

void FailedLeader::run() {
  runHelper("", _shard);
}

void FailedLeader::rollback() {

  // Create new plan servers (exchange _to and _from)
  std::string planPath
    = planColPrefix + _database + "/" + _collection + "/shards/" + _shard;
  auto const& planned = _snapshot(planPath).slice();

  VPackBuilder rb;
  if (!_to.empty()) {
    { VPackArrayBuilder r(&rb);
      for (auto const i : VPackArrayIterator(planned)) {
        TRI_ASSERT(i.isString());
        auto istr = i.copyString();
        if (istr == _from) {
          rb.add(VPackValue(_to));
        } else if (istr == _to) {
          rb.add(VPackValue(_from));
        } else {
          rb.add(i);
        }
      }
    }
  } else {
    rb.add(planned);
  }

  auto cs = clones(_snapshot, _database, _collection, _shard);

  // Transactions
  auto payload = std::make_shared<Builder>();
  { VPackObjectBuilder b(payload.get());
    for (auto const c : cs) {
      payload->add(planColPrefix + _database + "/" + c.collection + "/shards/" +
                   c.shard, rb.slice());
    }
  }

  finish("", _shard, false, "Timed out.", payload);

}

bool FailedLeader::create(std::shared_ptr<VPackBuilder> b) {

  using namespace std::chrono;
  LOG_TOPIC(INFO, Logger::SUPERVISION)
    << "Create failedLeader for " + _shard + " from " + _from;

  _jb = std::make_shared<Builder>();
  { VPackArrayBuilder transaction(_jb.get());
    { VPackObjectBuilder operations(_jb.get());
      // Todo entry
      _jb->add(VPackValue(toDoPrefix + _jobId));
      { VPackObjectBuilder todo(_jb.get());
        _jb->add("creator", VPackValue(_creator));
        _jb->add("type", VPackValue("failedLeader"));
        _jb->add("database", VPackValue(_database));
        _jb->add("collection", VPackValue(_collection));
        _jb->add("shard", VPackValue(_shard));
        _jb->add("fromServer", VPackValue(_from));
        _jb->add("jobId", VPackValue(_jobId));
        _jb->add(
          "timeCreated", VPackValue(timepointToString(system_clock::now())));
      }}}
  write_ret_t res = singleWriteTransaction(_agent, *_jb);
  return (res.accepted && res.indices.size() == 1 && res.indices[0]);

}

bool FailedLeader::start() {

  std::vector<std::string> existing =
    _snapshot.exists(planColPrefix + _database + "/" + _collection + "/" +
                     "distributeShardsLike");

  // Fail if got distributeShardsLike
  if (existing.size() == 5) {
    finish("", _shard, false, "Collection has distributeShardsLike");
    return false;
  }
  // Fail if collection gone
  else if (existing.size() < 4) {
    finish("", _shard, true, "Collection " + _collection + " gone");
    return false;
  }

  // Get healthy in Sync follower common to all prototype + clones
  auto commonHealthyInSync =
    findNonblockedCommonHealthyInSyncFollower(
      _snapshot, _database, _collection, _shard);
  if (commonHealthyInSync.empty()) {
    return false;
  } else {
    _to = commonHealthyInSync;
  }

  LOG_TOPIC(INFO, Logger::SUPERVISION)
    << "Start failedLeader for " + _shard + " from " + _from + " to " + _to;

  using namespace std::chrono;

  // Current servers vector
  auto const& current =
    _snapshot(
      curColPrefix + _database + "/" + _collection + "/" + _shard + "/servers")
    .slice();
  // Planned servers vector
  std::string planPath
    = planColPrefix + _database + "/" + _collection + "/shards/" + _shard;
  auto const& planned = _snapshot(planPath).slice();

  // Get todo entry
  Builder todo;
  { VPackArrayBuilder t(&todo);
    if (_jb == nullptr) {
      try {
        _snapshot(toDoPrefix + _jobId).toBuilder(todo);
      } catch (std::exception const&) {
        LOG_TOPIC(INFO, Logger::SUPERVISION)
          << "Failed to get key " + toDoPrefix + _jobId
          + " from agency snapshot";
        return false;
      }
    } else {
      todo.add(_jb->slice()[0].get(toDoPrefix + _jobId));
    }}

  // New plan vector excluding _to and _from
  std::vector<std::string> planv;
  for (auto const& i : VPackArrayIterator(planned)) {
    auto s = i.copyString();
    if (s != _from && s != _to) {
      planv.push_back(s);
    }
  }

  // Additional follower, if applicable
  auto additionalFollower = randomIdleGoodAvailableServer(_snapshot, planned);
  if (!additionalFollower.empty()) {
    planv.push_back(additionalFollower);
  }

  // Transactions
  Builder pending;

  { VPackArrayBuilder transactions(&pending);
    { VPackArrayBuilder transaction(&pending);

      // Operations ----------------------------------------------------------
      { VPackObjectBuilder operations(&pending);
        // Add pending entry
        pending.add(VPackValue(pendingPrefix + _jobId));
        { VPackObjectBuilder ts(&pending);
          pending.add("timeStarted", // start
                       VPackValue(timepointToString(system_clock::now())));
          pending.add("toServer", VPackValue(_to)); // toServer
          for (auto const& obj : VPackObjectIterator(todo.slice()[0])) {
            pending.add(obj.key.copyString(), obj.value);
          }
        }
        addRemoveJobFromSomewhere(pending, "ToDo", _jobId);
        // DB server vector -------
        Builder ns;
        { VPackArrayBuilder servers(&ns);
          ns.add(VPackValue(_to));
          for (auto const& i : VPackArrayIterator(current)) {
            std::string s = i.copyString();
            if (s != _from && s != _to) {
              ns.add(i);
              planv.erase(
                std::remove(planv.begin(), planv.end(), s), planv.end());
            }
          }
          ns.add(VPackValue(_from));
          for (auto const& i : planv) {
            ns.add(VPackValue(i));
          }
        }
        for (auto const& clone :
               clones(_snapshot, _database, _collection, _shard)) {
          pending.add(
            planColPrefix + _database + "/"
            + clone.collection + "/shards/" + clone.shard, ns.slice());
        }
        addBlockShard(pending, _shard, _jobId);
        addIncreasePlanVersion(pending);
      }
      // Preconditions -------------------------------------------------------
      { VPackObjectBuilder preconditions(&pending);
        // Failed condition persists
        pending.add(VPackValue(healthPrefix + _from + "/Status"));
        { VPackObjectBuilder stillExists(&pending);
          pending.add("old", VPackValue("FAILED")); }
        // Destination server still in good condition
        addPreconditionServerGood(pending, _to);
        // Server list in plan still as before
        addPreconditionUnchanged(pending, planPath, planned);
        // Destination server should not be blocked by another job
        addPreconditionServerNotBlocked(pending, _to);
        // Shard to be handled is block by another job
        addPreconditionShardNotBlocked(pending, _shard);
      } // Preconditions -----------------------------------------------------
    }
  }

  // Abort job blocking server if abortable
  try {
    std::string jobId = _snapshot(blockedShardsPrefix + _shard).getString();
    if (!abortable(_snapshot, jobId)) {
      return false;
    } else {
      JobContext(PENDING, jobId, _snapshot, _agent).abort();
    }
  } catch (...) {}

  LOG_TOPIC(DEBUG, Logger::SUPERVISION)
    << "FailedLeader transaction: " << pending.toJson();

  trans_ret_t res = generalTransaction(_agent, pending);

  LOG_TOPIC(DEBUG, Logger::SUPERVISION)
    << "FailedLeader result: " << res.result->toJson();

  // Something went south. Let's see
  auto result = res.result->slice()[0];

  if (res.accepted && result.isNumber()) {
    return true;
  }

  TRI_ASSERT(result.isObject());

  if (!res.accepted) { // lost leadership
    LOG_TOPIC(INFO, Logger::SUPERVISION)
      << "Leadership lost! Job " << _jobId << " handed off.";
  }

  if (result.isObject()) {

    // Still failing _from?
    auto slice = result.get(
      std::vector<std::string>({
          agencyPrefix, "Supervision", "Health", _from, "Status"}));
    if (!slice.isString() || slice.copyString() != "FAILED") {
      finish("", _shard, false, "Server " + _from + " no longer failing.");
      return false;
    }

    // Still healthy _to?
    slice = result.get(
      std::vector<std::string>(
        {agencyPrefix, "Supervision", "Health", _to, "Status"}));
    if (!slice.isString() || slice.copyString() != "GOOD") {
      LOG_TOPIC(INFO, Logger::SUPERVISION)
        << "Will not failover from " << _from << " to " << _to
        << " as target server is no longer in good condition. Will retry.";
      return false;
    }

    // Snapshot and plan still in sync with respect to server list?
    slice = result.get(
      std::vector<std::string>({agencyPrefix, "Plan", "Collections", _database,
            _collection, "shards", _shard}));
    if (!slice.isNone()) {
      LOG_TOPIC(INFO, Logger::SUPERVISION)
        << "Plan no longer holds the expected server list. Will retry.";
    }

    // To server blocked by other job?
    slice = result.get(
      std::vector<std::string>(
        {agencyPrefix, "Supervision", "DBServers", _to}));
    if (!slice.isNone()) {
      LOG_TOPIC(INFO, Logger::SUPERVISION)
        << "Destination server " << _to << " meanwhile is blocked by job " <<
        slice.copyString();
    }

    // This shard blocked by other job?
    slice = result.get(
      std::vector<std::string>(
        {agencyPrefix, "Supervision", "Shards", _shard}));
    if (!slice.isNone()) {
      LOG_TOPIC(INFO, Logger::SUPERVISION)
        << "Shard  " << _shard << " meanwhile is blocked by job " <<
        slice.copyString();
    }
  }

  return false;

}

JOB_STATUS FailedLeader::status() {

  if(!_snapshot.has(planColPrefix + _database + "/" + _collection)) {
    finish("", _shard, true, "Collection " + _collection + " gone");
    return FINISHED;
  }

  // Timedout after 77 minutes
  if (std::chrono::system_clock::now() - _created > std::chrono::seconds(4620)) {
    rollback();
  }

  if (_status != PENDING) {
    return _status;
  }

  Node const& job = _snapshot(pendingPrefix + _jobId);
  std::string database = job("database").toJson(),
    shard = job("shard").toJson();

  bool done = false;
  for (auto const& clone : clones(_snapshot, _database, _collection, _shard)) {
    auto sub = database + "/" + clone.collection;
    if(_snapshot(planColPrefix + sub + "/shards/" + clone.shard).slice()[0] !=
       _snapshot(curColPrefix + sub + "/" + clone.shard + "/servers").slice()[0]) {
      LOG_TOPIC(DEBUG, Logger::SUPERVISION)
        << "FailedLeader waiting for " << sub + "/" + shard;
      break;
    }
    done = true;
  }

  if (done) {
    // Remove shard to /arango/Target/FailedServers/<server> array
    Builder del;
    { VPackArrayBuilder a(&del);
      { VPackObjectBuilder o(&del);
        del.add(VPackValue(failedServersPrefix + "/" + _from));
        { VPackObjectBuilder erase(&del);
          del.add("op", VPackValue("erase"));
          del.add("val", VPackValue(_shard));
        }}}

    write_ret_t res = singleWriteTransaction(_agent, del);
    if (finish("", shard)) {
      LOG_TOPIC(INFO, Logger::SUPERVISION)
        << "Finished failedLeader for " + _shard + " from " + _from + " to " + _to;
        return FINISHED;
    }
  }

  return _status;
}

arangodb::Result FailedLeader::abort() {
  // job is only abortable when it is in ToDo
  if (_status != TODO) {
    return Result(TRI_ERROR_SUPERVISION_GENERAL_FAILURE,
                  "Failed aborting failedFollower job beyond todo stage");
  } else {
    finish("", "", false, "job aborted");
    return Result();
  }
}

<commit_msg>Fix abort conditions of FailedLeader<commit_after>////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Kaveh Vahedipour
////////////////////////////////////////////////////////////////////////////////

#include "FailedLeader.h"

#include "Agency/Agent.h"
#include "Agency/Job.h"
#include "Agency/JobContext.h"

#include <algorithm>
#include <vector>

using namespace arangodb::consensus;

FailedLeader::FailedLeader(Node const& snapshot, AgentInterface* agent,
                           std::string const& jobId, std::string const& creator,
                           std::string const& database,
                           std::string const& collection,
                           std::string const& shard, std::string const& from)
    : Job(NOTFOUND, snapshot, agent, jobId, creator), _database(database),
      _collection(collection), _shard(shard), _from(from) {}

FailedLeader::FailedLeader(
  Node const& snapshot, AgentInterface* agent, JOB_STATUS status,
  std::string const& jobId) : Job(status, snapshot, agent, jobId) {

  // Get job details from agency:
  try {
    std::string path = pos[status] + _jobId + "/";
    _database = _snapshot(path + "database").getString();
    _collection = _snapshot(path + "collection").getString();
    _from = _snapshot(path + "fromServer").getString();
    try {
      // set only if already started
      _to = _snapshot(path + "toServer").getString();
    } catch (...) {}
    _shard = _snapshot(path + "shard").getString();
    _creator = _snapshot(path + "creator").getString();
    _created = stringToTimepoint(_snapshot(path + "timeCreated").getString());
  } catch (std::exception const& e) {
    std::stringstream err;
    err << "Failed to find job " << _jobId << " in agency: " << e.what();
    LOG_TOPIC(ERR, Logger::SUPERVISION) << err.str();
    finish("", _shard, false, err.str());
    _status = FAILED;
  }

}

FailedLeader::~FailedLeader() {}

void FailedLeader::run() {
  runHelper("", _shard);
}

void FailedLeader::rollback() {

  // Create new plan servers (exchange _to and _from)
  std::string planPath
    = planColPrefix + _database + "/" + _collection + "/shards/" + _shard;
  auto const& planned = _snapshot(planPath).slice();

  VPackBuilder rb;
  if (!_to.empty()) {
    { VPackArrayBuilder r(&rb);
      for (auto const i : VPackArrayIterator(planned)) {
        TRI_ASSERT(i.isString());
        auto istr = i.copyString();
        if (istr == _from) {
          rb.add(VPackValue(_to));
        } else if (istr == _to) {
          rb.add(VPackValue(_from));
        } else {
          rb.add(i);
        }
      }
    }
  } else {
    rb.add(planned);
  }

  auto cs = clones(_snapshot, _database, _collection, _shard);

  // Transactions
  auto payload = std::make_shared<Builder>();
  { VPackObjectBuilder b(payload.get());
    for (auto const c : cs) {
      payload->add(planColPrefix + _database + "/" + c.collection + "/shards/" +
                   c.shard, rb.slice());
    }
  }

  finish("", _shard, false, "Timed out.", payload);

}

bool FailedLeader::create(std::shared_ptr<VPackBuilder> b) {

  using namespace std::chrono;
  LOG_TOPIC(INFO, Logger::SUPERVISION)
    << "Create failedLeader for " + _shard + " from " + _from;

  _jb = std::make_shared<Builder>();
  { VPackArrayBuilder transaction(_jb.get());
    { VPackObjectBuilder operations(_jb.get());
      // Todo entry
      _jb->add(VPackValue(toDoPrefix + _jobId));
      { VPackObjectBuilder todo(_jb.get());
        _jb->add("creator", VPackValue(_creator));
        _jb->add("type", VPackValue("failedLeader"));
        _jb->add("database", VPackValue(_database));
        _jb->add("collection", VPackValue(_collection));
        _jb->add("shard", VPackValue(_shard));
        _jb->add("fromServer", VPackValue(_from));
        _jb->add("jobId", VPackValue(_jobId));
        _jb->add(
          "timeCreated", VPackValue(timepointToString(system_clock::now())));
      }}}
  write_ret_t res = singleWriteTransaction(_agent, *_jb);
  return (res.accepted && res.indices.size() == 1 && res.indices[0]);

}

bool FailedLeader::start() {

  std::vector<std::string> existing =
    _snapshot.exists(planColPrefix + _database + "/" + _collection + "/" +
                     "distributeShardsLike");

  // Fail if got distributeShardsLike
  if (existing.size() == 5) {
    finish("", _shard, false, "Collection has distributeShardsLike");
    return false;
  }
  // Fail if collection gone
  else if (existing.size() < 4) {
    finish("", _shard, true, "Collection " + _collection + " gone");
    return false;
  }

  // Get healthy in Sync follower common to all prototype + clones
  auto commonHealthyInSync =
    findNonblockedCommonHealthyInSyncFollower(
      _snapshot, _database, _collection, _shard);
  if (commonHealthyInSync.empty()) {
    return false;
  } else {
    _to = commonHealthyInSync;
  }

  LOG_TOPIC(INFO, Logger::SUPERVISION)
    << "Start failedLeader for " + _shard + " from " + _from + " to " + _to;

  using namespace std::chrono;

  // Current servers vector
  auto const& current =
    _snapshot(
      curColPrefix + _database + "/" + _collection + "/" + _shard + "/servers")
    .slice();
  // Planned servers vector
  std::string planPath
    = planColPrefix + _database + "/" + _collection + "/shards/" + _shard;
  auto const& planned = _snapshot(planPath).slice();

  // Get todo entry
  Builder todo;
  { VPackArrayBuilder t(&todo);
    if (_jb == nullptr) {
      try {
        _snapshot(toDoPrefix + _jobId).toBuilder(todo);
      } catch (std::exception const&) {
        LOG_TOPIC(INFO, Logger::SUPERVISION)
          << "Failed to get key " + toDoPrefix + _jobId
          + " from agency snapshot";
        return false;
      }
    } else {
      todo.add(_jb->slice()[0].get(toDoPrefix + _jobId));
    }}

  // New plan vector excluding _to and _from
  std::vector<std::string> planv;
  for (auto const& i : VPackArrayIterator(planned)) {
    auto s = i.copyString();
    if (s != _from && s != _to) {
      planv.push_back(s);
    }
  }

  // Additional follower, if applicable
  auto additionalFollower = randomIdleGoodAvailableServer(_snapshot, planned);
  if (!additionalFollower.empty()) {
    planv.push_back(additionalFollower);
  }

  // Transactions
  Builder pending;

  { VPackArrayBuilder transactions(&pending);
    { VPackArrayBuilder transaction(&pending);

      // Operations ----------------------------------------------------------
      { VPackObjectBuilder operations(&pending);
        // Add pending entry
        pending.add(VPackValue(pendingPrefix + _jobId));
        { VPackObjectBuilder ts(&pending);
          pending.add("timeStarted", // start
                       VPackValue(timepointToString(system_clock::now())));
          pending.add("toServer", VPackValue(_to)); // toServer
          for (auto const& obj : VPackObjectIterator(todo.slice()[0])) {
            pending.add(obj.key.copyString(), obj.value);
          }
        }
        addRemoveJobFromSomewhere(pending, "ToDo", _jobId);
        // DB server vector -------
        Builder ns;
        { VPackArrayBuilder servers(&ns);
          ns.add(VPackValue(_to));
          for (auto const& i : VPackArrayIterator(current)) {
            std::string s = i.copyString();
            if (s != _from && s != _to) {
              ns.add(i);
              planv.erase(
                std::remove(planv.begin(), planv.end(), s), planv.end());
            }
          }
          ns.add(VPackValue(_from));
          for (auto const& i : planv) {
            ns.add(VPackValue(i));
          }
        }
        for (auto const& clone :
               clones(_snapshot, _database, _collection, _shard)) {
          pending.add(
            planColPrefix + _database + "/"
            + clone.collection + "/shards/" + clone.shard, ns.slice());
        }
        addBlockShard(pending, _shard, _jobId);
        addIncreasePlanVersion(pending);
      }
      // Preconditions -------------------------------------------------------
      { VPackObjectBuilder preconditions(&pending);
        // Failed condition persists
        pending.add(VPackValue(healthPrefix + _from + "/Status"));
        { VPackObjectBuilder stillExists(&pending);
          pending.add("old", VPackValue("FAILED")); }
        // Destination server still in good condition
        addPreconditionServerGood(pending, _to);
        // Server list in plan still as before
        addPreconditionUnchanged(pending, planPath, planned);
        // Destination server should not be blocked by another job
        addPreconditionServerNotBlocked(pending, _to);
        // Shard to be handled is block by another job
        addPreconditionShardNotBlocked(pending, _shard);
      } // Preconditions -----------------------------------------------------
    }
  }

  // Abort job blocking server if abortable
  try {
    std::string jobId = _snapshot(blockedShardsPrefix + _shard).getString();
    if (!abortable(_snapshot, jobId)) {
      return false;
    } else {
      JobContext(PENDING, jobId, _snapshot, _agent).abort();
    }
  } catch (...) {}

  LOG_TOPIC(DEBUG, Logger::SUPERVISION)
    << "FailedLeader transaction: " << pending.toJson();

  trans_ret_t res = generalTransaction(_agent, pending);

  LOG_TOPIC(DEBUG, Logger::SUPERVISION)
    << "FailedLeader result: " << res.result->toJson();

  // Something went south. Let's see
  auto result = res.result->slice()[0];

  if (res.accepted && result.isNumber()) {
    return true;
  }

  TRI_ASSERT(result.isObject());

  if (!res.accepted) { // lost leadership
    LOG_TOPIC(INFO, Logger::SUPERVISION)
      << "Leadership lost! Job " << _jobId << " handed off.";
  }

  if (result.isObject()) {

    // Still failing _from?
    auto slice = result.get(
      std::vector<std::string>({
          agencyPrefix, "Supervision", "Health", _from, "Status"}));
    if (slice.isString() && slice.copyString() == "GOOD") {
      finish("", _shard, false, "Server " + _from + " no longer failing.");
      return false;
    }

    // Still healthy _to?
    slice = result.get(
      std::vector<std::string>(
        {agencyPrefix, "Supervision", "Health", _to, "Status"}));
    if (slice.isString() && slice.copyString() != "GOOD") {
      LOG_TOPIC(INFO, Logger::SUPERVISION)
        << "Will not failover from " << _from << " to " << _to
        << " as target server is no longer in good condition. Will retry.";
      return false;
    }

    // Snapshot and plan still in sync with respect to server list?
    slice = result.get(
      std::vector<std::string>({agencyPrefix, "Plan", "Collections", _database,
            _collection, "shards", _shard}));
    if (!slice.isNone()) {
      LOG_TOPIC(INFO, Logger::SUPERVISION)
        << "Plan no longer holds the expected server list. Will retry.";
    }

    // To server blocked by other job?
    slice = result.get(
      std::vector<std::string>(
        {agencyPrefix, "Supervision", "DBServers", _to}));
    if (!slice.isNone()) {
      LOG_TOPIC(INFO, Logger::SUPERVISION)
        << "Destination server " << _to << " meanwhile is blocked by job " <<
        slice.copyString();
    }

    // This shard blocked by other job?
    slice = result.get(
      std::vector<std::string>(
        {agencyPrefix, "Supervision", "Shards", _shard}));
    if (!slice.isNone()) {
      LOG_TOPIC(INFO, Logger::SUPERVISION)
        << "Shard  " << _shard << " meanwhile is blocked by job " <<
        slice.copyString();
    }
  }

  return false;

}

JOB_STATUS FailedLeader::status() {

  if(!_snapshot.has(planColPrefix + _database + "/" + _collection)) {
    finish("", _shard, true, "Collection " + _collection + " gone");
    return FINISHED;
  }

  // Timedout after 77 minutes
  if (std::chrono::system_clock::now() - _created > std::chrono::seconds(4620)) {
    rollback();
  }

  if (_status != PENDING) {
    return _status;
  }

  Node const& job = _snapshot(pendingPrefix + _jobId);
  std::string database = job("database").toJson(),
    shard = job("shard").toJson();

  bool done = false;
  for (auto const& clone : clones(_snapshot, _database, _collection, _shard)) {
    auto sub = database + "/" + clone.collection;
    if(_snapshot(planColPrefix + sub + "/shards/" + clone.shard).slice()[0] !=
       _snapshot(curColPrefix + sub + "/" + clone.shard + "/servers").slice()[0]) {
      LOG_TOPIC(DEBUG, Logger::SUPERVISION)
        << "FailedLeader waiting for " << sub + "/" + shard;
      break;
    }
    done = true;
  }

  if (done) {
    // Remove shard to /arango/Target/FailedServers/<server> array
    Builder del;
    { VPackArrayBuilder a(&del);
      { VPackObjectBuilder o(&del);
        del.add(VPackValue(failedServersPrefix + "/" + _from));
        { VPackObjectBuilder erase(&del);
          del.add("op", VPackValue("erase"));
          del.add("val", VPackValue(_shard));
        }}}

    write_ret_t res = singleWriteTransaction(_agent, del);
    if (finish("", shard)) {
      LOG_TOPIC(INFO, Logger::SUPERVISION)
        << "Finished failedLeader for " + _shard + " from " + _from + " to " + _to;
        return FINISHED;
    }
  }

  return _status;
}

arangodb::Result FailedLeader::abort() {
  // job is only abortable when it is in ToDo
  if (_status != TODO) {
    return Result(TRI_ERROR_SUPERVISION_GENERAL_FAILURE,
                  "Failed aborting failedFollower job beyond todo stage");
  } else {
    finish("", "", false, "job aborted");
    return Result();
  }
}

<|endoftext|>


<commit_before>#include "HPACK.h"
#include "hpack_table.h"
#include "gtest/gtest.h"
#include <string.h>
#include "picojson/picojson.h"
#include <fstream>
#include <iostream>
#include <iterator>

using namespace picojson;

TEST(encode_intTest, NormalTest) {
    uint8_t dst[10];
    uint8_t expect[][5] = {
        {0x01, 0x00},
        {0x0f, 0x01},
        //{0x1f, 0xa1, 0x8d, 0xb7, 0x01},
    };
    uint64_t len = encode_int(dst, 1, 1);
    EXPECT_EQ(2, len);
    EXPECT_TRUE(0 == std::memcmp(dst, expect[0], len));
    len = encode_int(dst, 16, 4);
    EXPECT_EQ(2, len);
    EXPECT_TRUE(0 == std::memcmp(dst, expect[1], len));
    /*
      len = encode_int(dst, 3000000, 5);
      EXPECT_EQ(5, len);
      EXPECT_TRUE(0 == std::memcmp(dst, expect[1], len));
    */
}

TEST(decode_intTest, NormalTest) {
    uint32_t dst = 0;
    uint8_t data[][5] = {
        {0x01, 0x00},
        {0x0f, 0x01},
    };
    EXPECT_EQ(2, decode_int(dst, data[0], 1));
    EXPECT_EQ(1, dst);
    EXPECT_EQ(2, decode_int(dst, data[1], 4));
    EXPECT_EQ(16, dst);
}

const static std::string TestCases[] = {
    "hpack-test-case/haskell-http2-naive/",
    "hpack-test-case/haskell-http2-naive-huffman/",
    "hpack-test-case/haskell-http2-static/",
    "hpack-test-case/haskell-http2-static-huffman/",
    "hpack-test-case/haskell-http2-linear/",
    "hpack-test-case/haskell-http2-linear-huffman/",
    "hpack-test-case/go-hpack/",
    "hpack-test-case/nghttp2/",
    "hpack-test-case/nghttp2-16384-4096/",
    "hpack-test-case/nghttp2-change-table-size/",
    "hpack-test-case/node-http2-hpack/",
};

const static std::string out_tmp_file = "filename.txt";

bool
read_json_files(std::vector<std::string> &jsons, const std::string testcase) {
    std::string call_str = "ls " + testcase + " > " + out_tmp_file;
    int len = call_str.length();
    char call[70];
    memcpy(call, call_str.c_str(), len+1);
    system(call);
    std::ifstream fnames(out_tmp_file);
    if (fnames.fail()) {
        std::cerr  << "fail to open" << out_tmp_file << std::endl;
        return false;
    }

    std::string field;
    while (std::getline(fnames, field)) {
        jsons.push_back(field);
    }

    return true;
}

bool
read_json_as_pico(value& v, const std::string path) {
    std::ifstream ifs(path);

    if (ifs.fail()) {
        std::cerr  << "fail to open" << std::endl;
        return false;
    }
    std::string str((std::istreambuf_iterator<char>(ifs)),
                    std::istreambuf_iterator<char>());
    std::string err = parse(v, str);
    if (! err.empty()) {
        std::cerr << err << std::endl;
        return false;
    }
    return true;
}

bool
read_sequence(int& table_size, std::vector<header>& ans_headers, std::string& wire, array::iterator it_seqno) {
    object obj_in = it_seqno->get<object>();
    if (obj_in["header_table_size"].to_str() != "null") {
        table_size = (int)std::stoi(obj_in["header_table_size"].to_str());
    }
    wire = obj_in["wire"].to_str();
    array json_headers = obj_in["headers"].get<array>();
    array::iterator it_headers;

    for (it_headers = json_headers.begin(); it_headers != json_headers.end(); it_headers++) {
        object content = it_headers->get<object>();
        object::iterator it = content.begin();
        ans_headers.push_back(header(it->first, it->second.to_str()));
    }
    return true;
}

bool
wire2byte(uint8_t *wire_byte, const std::string wire) {
    int len = wire.length();
    for (int i = 0; i < len; i += 2) {
        *(wire_byte+i/2) = (uint8_t)std::stoi(wire.substr(i, 2).c_str(), nullptr, 16);
    }
    return true;
}

void
detect_testcase_type(bool &from_header, bool &from_static,
                     bool &is_huffman,const std::string testcase) {
    from_header = std::string::npos != testcase.find("linear", 0);
    from_static = from_header || std::string::npos != testcase.find("static", 0);
    if (std::string::npos != testcase.find("haskell", 0)) {
        is_huffman = std::string::npos != testcase.find("huffman", 0);
    } else {
        is_huffman = true;
    }
}

void
print_wires(const uint8_t* expect, uint64_t e_len, const uint8_t* actual, uint64_t a_len) {
    std::cout << "Expect" << std::endl;
    for (int i = 0; i < e_len; i++) {
        printf("%02x", *(expect+i));
    }
    std::cout << std::endl;
    std::cout << "Actual" << std::endl;
    for (int i = 0; i < a_len; i++) {
        printf("%02x", *(actual+i));
    }
    std::cout << std::endl << std::endl;
}

TEST(encodeTest, NormalTest) {
    for (const std::string testcase : TestCases) {
        std::vector<std::string> jsons;
        bool err = read_json_files(jsons, testcase);
        if (!err) {
        }

        bool from_header, from_static, is_huffman;
        detect_testcase_type(from_header, from_static, is_huffman, testcase);
        std::cout << testcase << " " << from_header << from_static << is_huffman << std::endl;

        Table* encode_table = new Table();
        Table* decode_table = new Table();
        for (std::string json_file : jsons) {
            value v;
            err = read_json_as_pico(v, testcase + json_file);
            if (!err) {
            }

            object obj = v.get<object>();
            array arr = obj["cases"].get<array>();
            array::iterator it_seqno = arr.begin();
            for (int seqno = 0; it_seqno != arr.end(); seqno++, it_seqno++) {
                int table_size = -1;
                std::string wire;
                std::vector<header> expect_headers;
                err = read_sequence(table_size, expect_headers, wire, it_seqno);
                if (!err) {
                }
                uint8_t *expect_wire = new uint8_t[wire.length()/2];
                err = wire2byte(expect_wire, wire);
                if (*expect_wire == 0x0e) {
                    *expect_wire = 0x08;
                }
                if (!err) {
                }

                uint8_t actual_wire[20000];
                int64_t len = hpack_encode(actual_wire, expect_headers, from_static,
                                           from_header, is_huffman, encode_table, table_size);

                int wire_assert = std::memcmp(actual_wire, expect_wire, len);
                if (wire_assert != 0) {
                    std::cout << testcase << json_file << "  seqno: " << seqno << std::endl;
                    print_wires(expect_wire, wire.length()/2, actual_wire, len);
                    for (header head : expect_headers) {
                        std::cout << head.first << " " << head.second << std::endl;
                    }
                }
                ASSERT_EQ(wire.length()/2, len);
                ASSERT_TRUE(0 == wire_assert);

                std::vector<header> actual_headers;
                int64_t cursor = hpack_decode(actual_headers, expect_wire, decode_table, wire.length()/2);
                ASSERT_EQ(cursor, wire.length()/2);
                ASSERT_EQ(actual_headers.size(), expect_headers.size());
                for (int i = 0; i < actual_headers.size(); i++) {
                    ASSERT_EQ(actual_headers[i], expect_headers[i]);
                }

                delete [] expect_wire;
            }
        }
        delete encode_table;
        delete decode_table;
    }
}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
<commit_msg>change testname<commit_after>#include "HPACK.h"
#include "hpack_table.h"
#include "gtest/gtest.h"
#include <string.h>
#include "picojson/picojson.h"
#include <fstream>
#include <iostream>
#include <iterator>

using namespace picojson;

TEST(encode_intTest, NormalTest) {
    uint8_t dst[10];
    uint8_t expect[][5] = {
        {0x01, 0x00},
        {0x0f, 0x01},
        //{0x1f, 0xa1, 0x8d, 0xb7, 0x01},
    };
    uint64_t len = encode_int(dst, 1, 1);
    EXPECT_EQ(2, len);
    EXPECT_TRUE(0 == std::memcmp(dst, expect[0], len));
    len = encode_int(dst, 16, 4);
    EXPECT_EQ(2, len);
    EXPECT_TRUE(0 == std::memcmp(dst, expect[1], len));
    /*
      len = encode_int(dst, 3000000, 5);
      EXPECT_EQ(5, len);
      EXPECT_TRUE(0 == std::memcmp(dst, expect[1], len));
    */
}

TEST(decode_intTest, NormalTest) {
    uint32_t dst = 0;
    uint8_t data[][5] = {
        {0x01, 0x00},
        {0x0f, 0x01},
    };
    EXPECT_EQ(2, decode_int(dst, data[0], 1));
    EXPECT_EQ(1, dst);
    EXPECT_EQ(2, decode_int(dst, data[1], 4));
    EXPECT_EQ(16, dst);
}

const static std::string TestCases[] = {
    "hpack-test-case/haskell-http2-naive/",
    "hpack-test-case/haskell-http2-naive-huffman/",
    "hpack-test-case/haskell-http2-static/",
    "hpack-test-case/haskell-http2-static-huffman/",
    "hpack-test-case/haskell-http2-linear/",
    "hpack-test-case/haskell-http2-linear-huffman/",
    "hpack-test-case/go-hpack/",
    "hpack-test-case/nghttp2/",
    "hpack-test-case/nghttp2-16384-4096/",
    "hpack-test-case/nghttp2-change-table-size/",
    "hpack-test-case/node-http2-hpack/",
};

const static std::string out_tmp_file = "filename.txt";

bool
read_json_files(std::vector<std::string> &jsons, const std::string testcase) {
    std::string call_str = "ls " + testcase + " > " + out_tmp_file;
    int len = call_str.length();
    char call[70];
    memcpy(call, call_str.c_str(), len+1);
    system(call);
    std::ifstream fnames(out_tmp_file);
    if (fnames.fail()) {
        std::cerr  << "fail to open" << out_tmp_file << std::endl;
        return false;
    }

    std::string field;
    while (std::getline(fnames, field)) {
        jsons.push_back(field);
    }

    return true;
}

bool
read_json_as_pico(value& v, const std::string path) {
    std::ifstream ifs(path);

    if (ifs.fail()) {
        std::cerr  << "fail to open" << std::endl;
        return false;
    }
    std::string str((std::istreambuf_iterator<char>(ifs)),
                    std::istreambuf_iterator<char>());
    std::string err = parse(v, str);
    if (! err.empty()) {
        std::cerr << err << std::endl;
        return false;
    }
    return true;
}

bool
read_sequence(int& table_size, std::vector<header>& ans_headers, std::string& wire, array::iterator it_seqno) {
    object obj_in = it_seqno->get<object>();
    if (obj_in["header_table_size"].to_str() != "null") {
        table_size = (int)std::stoi(obj_in["header_table_size"].to_str());
    }
    wire = obj_in["wire"].to_str();
    array json_headers = obj_in["headers"].get<array>();
    array::iterator it_headers;

    for (it_headers = json_headers.begin(); it_headers != json_headers.end(); it_headers++) {
        object content = it_headers->get<object>();
        object::iterator it = content.begin();
        ans_headers.push_back(header(it->first, it->second.to_str()));
    }
    return true;
}

bool
wire2byte(uint8_t *wire_byte, const std::string wire) {
    int len = wire.length();
    for (int i = 0; i < len; i += 2) {
        *(wire_byte+i/2) = (uint8_t)std::stoi(wire.substr(i, 2).c_str(), nullptr, 16);
    }
    return true;
}

void
detect_testcase_type(bool &from_header, bool &from_static,
                     bool &is_huffman,const std::string testcase) {
    from_header = std::string::npos != testcase.find("linear", 0);
    from_static = from_header || std::string::npos != testcase.find("static", 0);
    if (std::string::npos != testcase.find("haskell", 0)) {
        is_huffman = std::string::npos != testcase.find("huffman", 0);
    } else {
        is_huffman = true;
    }
}

void
print_wires(const uint8_t* expect, uint64_t e_len, const uint8_t* actual, uint64_t a_len) {
    std::cout << "Expect" << std::endl;
    for (int i = 0; i < e_len; i++) {
        printf("%02x", *(expect+i));
    }
    std::cout << std::endl;
    std::cout << "Actual" << std::endl;
    for (int i = 0; i < a_len; i++) {
        printf("%02x", *(actual+i));
    }
    std::cout << std::endl << std::endl;
}

TEST(HPACKeTest, NormalTest) {
    for (const std::string testcase : TestCases) {
        std::vector<std::string> jsons;
        bool err = read_json_files(jsons, testcase);
        if (!err) {
        }

        bool from_header, from_static, is_huffman;
        detect_testcase_type(from_header, from_static, is_huffman, testcase);
        std::cout << testcase << " " << from_header << from_static << is_huffman << std::endl;

        Table* encode_table = new Table();
        Table* decode_table = new Table();
        for (std::string json_file : jsons) {
            value v;
            err = read_json_as_pico(v, testcase + json_file);
            if (!err) {
            }

            object obj = v.get<object>();
            array arr = obj["cases"].get<array>();
            array::iterator it_seqno = arr.begin();
            for (int seqno = 0; it_seqno != arr.end(); seqno++, it_seqno++) {
                int table_size = -1;
                std::string wire;
                std::vector<header> expect_headers;
                err = read_sequence(table_size, expect_headers, wire, it_seqno);
                if (!err) {
                }
                uint8_t *expect_wire = new uint8_t[wire.length()/2];
                err = wire2byte(expect_wire, wire);
                if (*expect_wire == 0x0e) {
                    *expect_wire = 0x08;
                }
                if (!err) {
                }

                uint8_t actual_wire[20000];
                int64_t len = hpack_encode(actual_wire, expect_headers, from_static,
                                           from_header, is_huffman, encode_table, table_size);

                int wire_assert = std::memcmp(actual_wire, expect_wire, len);
                if (wire_assert != 0) {
                    std::cout << testcase << json_file << "  seqno: " << seqno << std::endl;
                    print_wires(expect_wire, wire.length()/2, actual_wire, len);
                    for (header head : expect_headers) {
                        std::cout << head.first << " " << head.second << std::endl;
                    }
                }
                ASSERT_EQ(wire.length()/2, len);
                ASSERT_TRUE(0 == wire_assert);

                std::vector<header> actual_headers;
                int64_t cursor = hpack_decode(actual_headers, expect_wire, decode_table, wire.length()/2);
                ASSERT_EQ(cursor, wire.length()/2);
                ASSERT_EQ(actual_headers.size(), expect_headers.size());
                for (int i = 0; i < actual_headers.size(); i++) {
                    ASSERT_EQ(actual_headers[i], expect_headers[i]);
                }

                delete [] expect_wire;
            }
        }
        delete encode_table;
        delete decode_table;
    }
}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
<|endoftext|>


<commit_before>/// Le Plan:
/// 1. Proof of concept.
/// 2. ???
/// 3. PROFIT!

// blackhole/sink/asynchronous.hpp

#include <atomic>
#include <thread>

#include <cds/container/vyukov_mpmc_cycle_queue.h>

#include "blackhole/sink.hpp"

#include "blackhole/detail/recordbuf.hpp"

namespace blackhole {
inline namespace v1 {
namespace experimental {
namespace sink {

using detail::recordbuf_t;

class asynchronous_t : public sink_t {
    struct value_type {
        recordbuf_t record;
        std::string message;
    };

    typedef cds::container::VyukovMPSCCycleQueue<value_type> queue_type;

    queue_type queue;
    std::atomic<bool> stopped;
    std::unique_ptr<sink_t> wrapped;

    std::thread thread;

public:
    /// \param factor queue capacity factor from which the queue capacity will be generated as
    ///     a value of `2 << factor`. Must fit in [0; 20] range (64 MB).
    /// \param queue_type
    /// \param overflow_policy [drop silently, drop with error, block]
    ///
    /// \throw std::invalid_argument if the factor is greater than 20.
    asynchronous_t(std::unique_ptr<sink_t> wrapped, std::size_t factor = 10);

    ~asynchronous_t();

    auto emit(const record_t& record, const string_view& message) -> void;

private:
    auto run() -> void;
};

}  // namespace sink
}  // namespace experimental
}  // namespace v1
}  // namespace blackhole

// src/sink/asynchronous.cpp

#include <unistd.h>

#include <cmath>

namespace blackhole {
inline namespace v1 {
namespace experimental {
namespace sink {
namespace {

static auto exp2(std::size_t factor) -> std::size_t {
    if (factor > 20) {
        throw std::invalid_argument("factor should fit in [0; 20] range");
    }

    return std::exp2(factor);
}

}  // namespace

asynchronous_t::asynchronous_t(std::unique_ptr<sink_t> wrapped, std::size_t factor) :
    queue(exp2(factor)),
    stopped(false),
    wrapped(std::move(wrapped)),
    thread(std::bind(&asynchronous_t::run, this))
{}

asynchronous_t::~asynchronous_t() {
    stopped.store(true);
    thread.join();
}

auto asynchronous_t::emit(const record_t& record, const string_view& message) -> void {
    if (stopped) {
        return;
    }

    const auto enqueued = queue.enqueue_with([&](value_type& value) {
        value = {recordbuf_t(record), message.to_string()};
    });

    // TODO: If failed, some kind of overflow policy is immediately involved.
}

auto asynchronous_t::run() -> void {
    while (true) {
        value_type result;
        const auto dequeued = queue.dequeue_with([&](value_type& value) {
            result = std::move(value);
        });

        if (dequeued) {
            // TODO: What to do with exceptions?
            wrapped->emit(result.record.into_view(), result.message);
        } else {
            // Sleep or CV.
            ::usleep(1000);
        }

        if (stopped && !dequeued) {
            return;
        }
    }
}

}  // namespace sink
}  // namespace experimental
}  // namespace v1
}  // namespace blackhole

// unit/sink/asynchronous.cpp

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "mocks/sink.hpp"

namespace blackhole {
inline namespace v1 {
namespace experimental {
namespace sink {
namespace {

using ::testing::_;
using ::testing::Invoke;

namespace mock = testing::mock;

TEST(asynchronous_t, DelegatesEmit) {
    std::unique_ptr<mock::sink_t> wrapped(new mock::sink_t);

    EXPECT_CALL(*wrapped, emit(_, string_view("formatted message")))
        .Times(1)
        .WillOnce(Invoke([](const record_t& record, string_view) {
            ASSERT_EQ(1, record.attributes().size());
            EXPECT_EQ((attribute_list{{"key#1", {"value#1"}}}), record.attributes().at(0).get());
        }));

    asynchronous_t sink(std::move(wrapped));

    const string_view message("unformatted message");
    const attribute_list attributes{{"key#1", {"value#1"}}};
    const attribute_pack pack({attributes});
    record_t record(42, message, pack);

    sink.emit(record, "formatted message");
}

}  // namespace
}  // namespace sink
}  // namespace experimental
}  // namespace v1
}  // namespace blackhole
<commit_msg>feat(sink/asynchronous): add more policies<commit_after>/// Le Plan:
/// 1. Proof of concept.
/// 2. ???
/// 3. PROFIT!

// blackhole/sink/asynchronous.hpp

#include <atomic>
#include <thread>

#include <cds/container/vyukov_mpmc_cycle_queue.h>

#include "blackhole/sink.hpp"

#include "blackhole/detail/recordbuf.hpp"

namespace blackhole {
inline namespace v1 {
namespace experimental {
namespace sink {

using detail::recordbuf_t;

/// I can imagine: drop, sleep, wait.
class overflow_policy_t {
public:
    enum class action_t {
        retry,
        drop
    };

public:
    /// Handles record queue overflow.
    ///
    /// This method is called when the queue is unable to enqueue more items. It's okay to throw
    /// exceptions from here, they will be propagated directly to the sink caller.
    virtual auto overflow() -> action_t = 0;

    virtual auto wakeup() -> void = 0;
};

class asynchronous_t : public sink_t {
    struct value_type {
        recordbuf_t record;
        std::string message;
    };

    typedef cds::container::VyukovMPSCCycleQueue<value_type> queue_type;

    queue_type queue;
    std::atomic<bool> stopped;
    std::unique_ptr<sink_t> wrapped;

    std::unique_ptr<overflow_policy_t> overflow_policy;

    std::thread thread;

public:
    /// \param factor queue capacity factor from which the queue capacity will be generated as
    ///     a value of `2 << factor`. Must fit in [0; 20] range (64 MB).
    /// \param queue_type
    /// \param overflow_policy [drop silently, drop with error, block]
    ///
    /// \throw std::invalid_argument if the factor is greater than 20.
    asynchronous_t(std::unique_ptr<sink_t> wrapped, std::size_t factor = 10);

    // asynchronous_t(std::unique_ptr<sink_t> sink,
    //                std::unique_ptr<filter_t> filter,
    //                std::unique_ptr<overflow_policy_t> overflow_policy,
    //                std::unique_ptr<exception_policy_t> exception_policy,
    //                std::size_t factor = 10);

    ~asynchronous_t();

    auto emit(const record_t& record, const string_view& message) -> void;

private:
    auto run() -> void;
};

}  // namespace sink
}  // namespace experimental
}  // namespace v1
}  // namespace blackhole

// src/sink/asynchronous.cpp

#include <unistd.h>

#include <cmath>

namespace blackhole {
inline namespace v1 {
namespace experimental {
namespace sink {
namespace {

static auto exp2(std::size_t factor) -> std::size_t {
    if (factor > 20) {
        throw std::invalid_argument("factor should fit in [0; 20] range");
    }

    return std::exp2(factor);
}

}  // namespace

class drop_overflow_policy_t : public overflow_policy_t {
    typedef overflow_policy_t::action_t action_t;

public:
    /// Drops on overlow.
    virtual auto overflow() -> action_t {
        return action_t::drop;
    }

    /// Does nothing on wakeup.
    virtual auto wakeup() -> void {}
};

class wait_overflow_policy_t : public overflow_policy_t {
    typedef overflow_policy_t::action_t action_t;

    mutable std::mutex mutex;
    std::condition_variable cv;

public:
    virtual auto overflow() -> action_t {
        std::unique_lock<std::mutex> lock(mutex);
        cv.wait(lock);
        return action_t::retry;
    }

    virtual auto wakeup() -> void {
        cv.notify_one();
    }
};

asynchronous_t::asynchronous_t(std::unique_ptr<sink_t> wrapped, std::size_t factor) :
    queue(exp2(factor)),
    stopped(false),
    wrapped(std::move(wrapped)),
    overflow_policy(new wait_overflow_policy_t),
    thread(std::bind(&asynchronous_t::run, this))
{}

asynchronous_t::~asynchronous_t() {
    stopped.store(true);
    thread.join();
}

auto asynchronous_t::emit(const record_t& record, const string_view& message) -> void {
    if (stopped) {
        throw std::logic_error("queue is sealed");
    }

    while (true) {
        const auto enqueued = queue.enqueue_with([&](value_type& value) {
            value = {recordbuf_t(record), message.to_string()};
        });

        if (enqueued) {
            // TODO: underflow_policy->wakeup();
            return;
        } else {
            switch (overflow_policy->overflow()) {
            case overflow_policy_t::action_t::retry:
                continue;
            case overflow_policy_t::action_t::drop:
                return;
            default:
                BOOST_ASSERT(false);
            }
        }
    }
}

auto asynchronous_t::run() -> void {
    while (true) {
        value_type result;
        const auto dequeued = queue.dequeue_with([&](value_type& value) {
            result = std::move(value);
        });

        if (dequeued) {
            try {
                wrapped->emit(result.record.into_view(), result.message);
                overflow_policy->wakeup();
            } catch (...) {
                // TODO: exception_policy->process(std::current_exception()); []
            }

        } else {
            ::usleep(1000);
            // TODO: underflow_policy->underflow(); [wait for enqueue, sleep].
        }

        if (stopped && !dequeued) {
            return;
        }
    }
}

}  // namespace sink
}  // namespace experimental
}  // namespace v1
}  // namespace blackhole

// unit/sink/asynchronous.cpp

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "mocks/sink.hpp"

namespace blackhole {
inline namespace v1 {
namespace experimental {
namespace sink {
namespace {

using ::testing::_;
using ::testing::Invoke;

namespace mock = testing::mock;

TEST(asynchronous_t, DelegatesEmit) {
    std::unique_ptr<mock::sink_t> wrapped(new mock::sink_t);

    EXPECT_CALL(*wrapped, emit(_, string_view("formatted message")))
        .Times(1)
        .WillOnce(Invoke([](const record_t& record, string_view) {
            ASSERT_EQ(1, record.attributes().size());
            EXPECT_EQ((attribute_list{{"key#1", {"value#1"}}}), record.attributes().at(0).get());
        }));

    asynchronous_t sink(std::move(wrapped));

    const string_view message("unformatted message");
    const attribute_list attributes{{"key#1", {"value#1"}}};
    const attribute_pack pack({attributes});
    record_t record(42, message, pack);

    sink.emit(record, "formatted message");
}

}  // namespace
}  // namespace sink
}  // namespace experimental
}  // namespace v1
}  // namespace blackhole
<|endoftext|>


<commit_before>// $Id$
//
// Test Suite for C-API GEOSCoordSeq

#include <tut.hpp>
// geos
#include <geos_c.h>
// std
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <memory>

namespace tut
{
    //
    // Test Group
    //

    // Common data used in test cases.
    struct test_capigeoscoordseq_data
    {

        GEOSCoordSequence* cs_;

        static void notice(const char *fmt, ...)
        {
            std::fprintf( stdout, "NOTICE: ");

            va_list ap;
            va_start(ap, fmt);
            std::vfprintf(stdout, fmt, ap);
            va_end(ap);

            std::fprintf(stdout, "\n");
        }

        test_capigeoscoordseq_data() : cs_(0)
        {
            initGEOS(notice, notice);
        }

        ~test_capigeoscoordseq_data()
        {
            GEOSCoordSeq_destroy(cs_);
            cs_ = 0;
            finishGEOS();
        }

    };

    typedef test_group<test_capigeoscoordseq_data> group;
    typedef group::object object;

    group test_capigeoscoordseq_group("capi::GEOSCoordSeq");

    //
    // Test Cases
    //

    // Test construction and fill of a 3D CoordinateSequence
    template<>
    template<>
    void object::test<1>()
    {
        cs_ = GEOSCoordSeq_create(5, 3);

        unsigned int size;
        unsigned int dims;

        ensure ( 0 != GEOSCoordSeq_getSize(cs_, &size) );
        ensure_equals( size, 5u );

        ensure ( 0 != GEOSCoordSeq_getDimensions(cs_, &dims) );
        ensure_equals( dims, 3u );

        for (unsigned int i=0; i<5; ++i)
        {
            double x = i*10;
            double y = i*10+1;
            double z = i*10+2;

            GEOSCoordSeq_setX(cs_, i, x);
            GEOSCoordSeq_setY(cs_, i, y);
            GEOSCoordSeq_setZ(cs_, i, z);

            double xcheck, ycheck, zcheck;
            ensure( 0 != GEOSCoordSeq_getX(cs_, i, &xcheck) );
            ensure( 0 != GEOSCoordSeq_getY(cs_, i, &ycheck) );
            ensure( 0 != GEOSCoordSeq_getZ(cs_, i, &zcheck) );

            ensure_equals( xcheck, x );
            ensure_equals( ycheck, y );
            ensure_equals( zcheck, z );
        }
    }

    // Test not swapped setX/setY calls (see bug #133, fixed)
    template<>
    template<>
    void object::test<2>()
    {
        cs_ = GEOSCoordSeq_create(1, 3);

        unsigned int size;
        unsigned int dims;

        ensure ( 0 != GEOSCoordSeq_getSize(cs_, &size) );
        ensure_equals( size, 1u );

        ensure ( 0 != GEOSCoordSeq_getDimensions(cs_, &dims) );
        ensure_equals( dims, 3u );

        double x = 10;
        double y = 11;
        double z = 12;

        // X, Y, Z
        GEOSCoordSeq_setX(cs_, 0, x);
        GEOSCoordSeq_setY(cs_, 0, y);
        GEOSCoordSeq_setZ(cs_, 0, z);

        double xcheck, ycheck, zcheck;
        ensure( 0 != GEOSCoordSeq_getY(cs_, 0, &ycheck) );
        ensure( 0 != GEOSCoordSeq_getX(cs_, 0, &xcheck) );
        ensure( 0 != GEOSCoordSeq_getZ(cs_, 0, &zcheck) );

        ensure_equals( xcheck, x );
        ensure_equals( ycheck, y );
        ensure_equals( zcheck, z );
    }

    // Test not swapped setOrdinate calls (see bug #133, fixed)
    template<>
    template<>
    void object::test<3>()
    {
        cs_ = GEOSCoordSeq_create(1, 3);

        unsigned int size;
        unsigned int dims;

        ensure ( 0 != GEOSCoordSeq_getSize(cs_, &size) );
        ensure_equals( size, 1u );

        ensure ( 0 != GEOSCoordSeq_getDimensions(cs_, &dims) );
        ensure_equals( dims, 3u );

        double x = 10;
        double y = 11;
        double z = 12;

        // X, Y, Z
        GEOSCoordSeq_setOrdinate(cs_, 0, 0, x);
        GEOSCoordSeq_setOrdinate(cs_, 0, 1, y);
        GEOSCoordSeq_setOrdinate(cs_, 0, 2, z);

        double xcheck, ycheck, zcheck;
        ensure( 0 != GEOSCoordSeq_getOrdinate(cs_, 0, 1, &ycheck) );
        ensure( 0 != GEOSCoordSeq_getOrdinate(cs_, 0, 0, &xcheck) );
        ensure( 0 != GEOSCoordSeq_getOrdinate(cs_, 0, 2, &zcheck) );

        ensure_equals( xcheck, x );
        ensure_equals( ycheck, y );
        ensure_equals( zcheck, z );
    }

    // Test swapped setX calls (see bug #133, fixed)
    template<>
    template<>
    void object::test<4>()
    {
        cs_ = GEOSCoordSeq_create(1, 3);

        unsigned int size;
        unsigned int dims;

        ensure ( 0 != GEOSCoordSeq_getSize(cs_, &size) );
        ensure_equals( size, 1u );

        ensure ( 0 != GEOSCoordSeq_getDimensions(cs_, &dims) );
        ensure_equals( dims, 3u );

        double x = 10;
        double y = 11;
        double z = 12;

        // Y, X, Z
        GEOSCoordSeq_setY(cs_, 0, y);
        GEOSCoordSeq_setX(cs_, 0, x);
        GEOSCoordSeq_setZ(cs_, 0, z);

        double xcheck, ycheck, zcheck;
        ensure( 0 != GEOSCoordSeq_getY(cs_, 0, &ycheck) );
        ensure( 0 != GEOSCoordSeq_getX(cs_, 0, &xcheck) );
        ensure( 0 != GEOSCoordSeq_getZ(cs_, 0, &zcheck) );

        ensure_equals( xcheck, x );
        ensure_equals( ycheck, y );
        ensure_equals( zcheck, z );
    }

    // Test swapped setOrdinate calls (see bug #133, fixed)
    template<>
    template<>
    void object::test<5>()
    {
        cs_ = GEOSCoordSeq_create(1, 3);

        unsigned int size;
        unsigned int dims;

        ensure ( 0 != GEOSCoordSeq_getSize(cs_, &size) );
        ensure_equals( size, 1u );

        ensure ( 0 != GEOSCoordSeq_getDimensions(cs_, &dims) );
        ensure_equals( dims, 3u );

        double x = 10;
        double y = 11;
        double z = 12;

        // Y, X, Z
        GEOSCoordSeq_setOrdinate(cs_, 0, 1, y);
        GEOSCoordSeq_setOrdinate(cs_, 0, 0, x);
        GEOSCoordSeq_setOrdinate(cs_, 0, 2, z);

        double xcheck, ycheck, zcheck;
        ensure( 0 != GEOSCoordSeq_getOrdinate(cs_, 0, 1, &ycheck) );
        ensure( 0 != GEOSCoordSeq_getOrdinate(cs_, 0, 0, &xcheck) );
        ensure( 0 != GEOSCoordSeq_getOrdinate(cs_, 0, 2, &zcheck) );

        ensure_equals( xcheck, x );
        ensure_equals( ycheck, y );
        ensure_equals( zcheck, z );
    }

} // namespace tut

<commit_msg>Add test for creating a CoordinateSequence with at least 2 dimension<commit_after>// $Id$
//
// Test Suite for C-API GEOSCoordSeq

#include <tut.hpp>
// geos
#include <geos_c.h>
// std
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <memory>

namespace tut
{
    //
    // Test Group
    //

    // Common data used in test cases.
    struct test_capigeoscoordseq_data
    {

        GEOSCoordSequence* cs_;

        static void notice(const char *fmt, ...)
        {
            std::fprintf( stdout, "NOTICE: ");

            va_list ap;
            va_start(ap, fmt);
            std::vfprintf(stdout, fmt, ap);
            va_end(ap);

            std::fprintf(stdout, "\n");
        }

        test_capigeoscoordseq_data() : cs_(0)
        {
            initGEOS(notice, notice);
        }

        ~test_capigeoscoordseq_data()
        {
            GEOSCoordSeq_destroy(cs_);
            cs_ = 0;
            finishGEOS();
        }

    };

    typedef test_group<test_capigeoscoordseq_data> group;
    typedef group::object object;

    group test_capigeoscoordseq_group("capi::GEOSCoordSeq");

    //
    // Test Cases
    //

    // Test construction and fill of a 3D CoordinateSequence
    template<>
    template<>
    void object::test<1>()
    {
        cs_ = GEOSCoordSeq_create(5, 3);

        unsigned int size;
        unsigned int dims;

        ensure ( 0 != GEOSCoordSeq_getSize(cs_, &size) );
        ensure_equals( size, 5u );

        ensure ( 0 != GEOSCoordSeq_getDimensions(cs_, &dims) );
        ensure_equals( dims, 3u );

        for (unsigned int i=0; i<5; ++i)
        {
            double x = i*10;
            double y = i*10+1;
            double z = i*10+2;

            GEOSCoordSeq_setX(cs_, i, x);
            GEOSCoordSeq_setY(cs_, i, y);
            GEOSCoordSeq_setZ(cs_, i, z);

            double xcheck, ycheck, zcheck;
            ensure( 0 != GEOSCoordSeq_getX(cs_, i, &xcheck) );
            ensure( 0 != GEOSCoordSeq_getY(cs_, i, &ycheck) );
            ensure( 0 != GEOSCoordSeq_getZ(cs_, i, &zcheck) );

            ensure_equals( xcheck, x );
            ensure_equals( ycheck, y );
            ensure_equals( zcheck, z );
        }
    }

    // Test not swapped setX/setY calls (see bug #133, fixed)
    template<>
    template<>
    void object::test<2>()
    {
        cs_ = GEOSCoordSeq_create(1, 3);

        unsigned int size;
        unsigned int dims;

        ensure ( 0 != GEOSCoordSeq_getSize(cs_, &size) );
        ensure_equals( size, 1u );

        ensure ( 0 != GEOSCoordSeq_getDimensions(cs_, &dims) );
        ensure_equals( dims, 3u );

        double x = 10;
        double y = 11;
        double z = 12;

        // X, Y, Z
        GEOSCoordSeq_setX(cs_, 0, x);
        GEOSCoordSeq_setY(cs_, 0, y);
        GEOSCoordSeq_setZ(cs_, 0, z);

        double xcheck, ycheck, zcheck;
        ensure( 0 != GEOSCoordSeq_getY(cs_, 0, &ycheck) );
        ensure( 0 != GEOSCoordSeq_getX(cs_, 0, &xcheck) );
        ensure( 0 != GEOSCoordSeq_getZ(cs_, 0, &zcheck) );

        ensure_equals( xcheck, x );
        ensure_equals( ycheck, y );
        ensure_equals( zcheck, z );
    }

    // Test not swapped setOrdinate calls (see bug #133, fixed)
    template<>
    template<>
    void object::test<3>()
    {
        cs_ = GEOSCoordSeq_create(1, 3);

        unsigned int size;
        unsigned int dims;

        ensure ( 0 != GEOSCoordSeq_getSize(cs_, &size) );
        ensure_equals( size, 1u );

        ensure ( 0 != GEOSCoordSeq_getDimensions(cs_, &dims) );
        ensure_equals( dims, 3u );

        double x = 10;
        double y = 11;
        double z = 12;

        // X, Y, Z
        GEOSCoordSeq_setOrdinate(cs_, 0, 0, x);
        GEOSCoordSeq_setOrdinate(cs_, 0, 1, y);
        GEOSCoordSeq_setOrdinate(cs_, 0, 2, z);

        double xcheck, ycheck, zcheck;
        ensure( 0 != GEOSCoordSeq_getOrdinate(cs_, 0, 1, &ycheck) );
        ensure( 0 != GEOSCoordSeq_getOrdinate(cs_, 0, 0, &xcheck) );
        ensure( 0 != GEOSCoordSeq_getOrdinate(cs_, 0, 2, &zcheck) );

        ensure_equals( xcheck, x );
        ensure_equals( ycheck, y );
        ensure_equals( zcheck, z );
    }

    // Test swapped setX calls (see bug #133, fixed)
    template<>
    template<>
    void object::test<4>()
    {
        cs_ = GEOSCoordSeq_create(1, 3);

        unsigned int size;
        unsigned int dims;

        ensure ( 0 != GEOSCoordSeq_getSize(cs_, &size) );
        ensure_equals( size, 1u );

        ensure ( 0 != GEOSCoordSeq_getDimensions(cs_, &dims) );
        ensure_equals( dims, 3u );

        double x = 10;
        double y = 11;
        double z = 12;

        // Y, X, Z
        GEOSCoordSeq_setY(cs_, 0, y);
        GEOSCoordSeq_setX(cs_, 0, x);
        GEOSCoordSeq_setZ(cs_, 0, z);

        double xcheck, ycheck, zcheck;
        ensure( 0 != GEOSCoordSeq_getY(cs_, 0, &ycheck) );
        ensure( 0 != GEOSCoordSeq_getX(cs_, 0, &xcheck) );
        ensure( 0 != GEOSCoordSeq_getZ(cs_, 0, &zcheck) );

        ensure_equals( xcheck, x );
        ensure_equals( ycheck, y );
        ensure_equals( zcheck, z );
    }

    // Test swapped setOrdinate calls (see bug #133, fixed)
    template<>
    template<>
    void object::test<5>()
    {
        cs_ = GEOSCoordSeq_create(1, 3);

        unsigned int size;
        unsigned int dims;

        ensure ( 0 != GEOSCoordSeq_getSize(cs_, &size) );
        ensure_equals( size, 1u );

        ensure ( 0 != GEOSCoordSeq_getDimensions(cs_, &dims) );
        ensure_equals( dims, 3u );

        double x = 10;
        double y = 11;
        double z = 12;

        // Y, X, Z
        GEOSCoordSeq_setOrdinate(cs_, 0, 1, y);
        GEOSCoordSeq_setOrdinate(cs_, 0, 0, x);
        GEOSCoordSeq_setOrdinate(cs_, 0, 2, z);

        double xcheck, ycheck, zcheck;
        ensure( 0 != GEOSCoordSeq_getOrdinate(cs_, 0, 1, &ycheck) );
        ensure( 0 != GEOSCoordSeq_getOrdinate(cs_, 0, 0, &xcheck) );
        ensure( 0 != GEOSCoordSeq_getOrdinate(cs_, 0, 2, &zcheck) );

        ensure_equals( xcheck, x );
        ensure_equals( ycheck, y );
        ensure_equals( zcheck, z );
    }

    // Test getDimensions call (see bug #135)
    template<>
    template<>
    void object::test<6>()
    {
        cs_ = GEOSCoordSeq_create(1, 2);

        unsigned int size;
        unsigned int dims;

        ensure ( 0 != GEOSCoordSeq_getSize(cs_, &size) );
        ensure_equals( size, 1u );

        ensure ( 0 != GEOSCoordSeq_getDimensions(cs_, &dims) );

	// The dimension passed to GEOSCoordSeq_create()
	// is a request for a minimum, not a strict mandate
	// for changing actual size.
	//
        ensure ( dims >= 2u );

    }

} // namespace tut

<|endoftext|>


<commit_before>#include "MenuState.h"
#include <iostream>
#include "TextureManager.h"
#include "Game.h"
#include "MenuButton.h"

const std::string MenuState::s_menuID = "MENU";

//function to be called when PLAY button is pressed
void MenuState::s_menuToPlay()
{
	std::cout << "Play button clicked\n";
}

//function to be called when EXIT button is pressed
void MenuState::s_exitFromMenu()
{
	//exit the game
	TheGame::Instance()->quit();
}

//update each game object
void MenuState::update()
{
	for (int i = 0; i < (int)m_gameObjects.size(); ++i)
	{
		m_gameObjects[i]->update();
	}
}

//draw the game objects
void MenuState::render()
{
	for (int i = 0; i < (int)m_gameObjects.size(); ++i)
	{
		m_gameObjects[i]->draw();
	}
}

bool MenuState::onEnter()
{
	//load the play button image
	if (!TheTextureManager::Instance()->load("assets/button.png", "playbutton", TheGame::Instance()->getRenderer()))
	{
		return false;
	}

	//load the exit button image
	if (!TheTextureManager::Instance()->load("assets/exit.png", "exitbutton", TheGame::Instance()->getRenderer()))
	{
		return false;
	}

	//assign the new textures to game objects named button1 and button2
	GameObject* button1 = new MenuButton(new LoaderParams(100, 100, 400, 100, "playbutton"), s_menuToPlay);
	GameObject* button2 = new MenuButton(new LoaderParams(100, 300, 400, 100, "exitbutton"), s_exitFromMenu);

	//push them in the vector
	m_gameObjects.push_back(button1);
	m_gameObjects.push_back(button2);

	std::cout << "entering MenuState\n";
	return true;
}

bool MenuState::onExit()
{
	//iterate through all the objects and clean
	for (int i = 0; i < (int)m_gameObjects.size(); ++i)
	{
		m_gameObjects[i]->clean();
	}
	m_gameObjects.clear();

	//remove the button textures
	TheTextureManager::Instance()->clearFromTextureMap("playbutton");
	TheTextureManager::Instance()->clearFromTextureMap("exitbutton");

	std::cout << "exiting MenuState\n";
	return true;
}<commit_msg>s_menuToPlay() function now creates a blank PlayState<commit_after>#include "MenuState.h"
#include <iostream>
#include "TextureManager.h"
#include "Game.h"
#include "MenuButton.h"
#include "PlayState.h"

const std::string MenuState::s_menuID = "MENU";

//function to be called when PLAY button is pressed
void MenuState::s_menuToPlay()
{
	//change the state to Play State
	TheGame::Instance()->getStateMachine()->changeState(new PlayState());
}

//function to be called when EXIT button is pressed
void MenuState::s_exitFromMenu()
{
	//exit the game
	TheGame::Instance()->quit();
}

//update each game object
void MenuState::update()
{
	for (int i = 0; i < (int)m_gameObjects.size(); ++i)
	{
		m_gameObjects[i]->update();
	}
}

//draw the game objects
void MenuState::render()
{
	for (int i = 0; i < (int)m_gameObjects.size(); ++i)
	{
		m_gameObjects[i]->draw();
	}
}

bool MenuState::onEnter()
{
	//load the play button image
	if (!TheTextureManager::Instance()->load("assets/button.png", "playbutton", TheGame::Instance()->getRenderer()))
	{
		return false;
	}

	//load the exit button image
	if (!TheTextureManager::Instance()->load("assets/exit.png", "exitbutton", TheGame::Instance()->getRenderer()))
	{
		return false;
	}

	//assign the new textures to game objects named button1 and button2
	GameObject* button1 = new MenuButton(new LoaderParams(100, 100, 400, 100, "playbutton"), s_menuToPlay);
	GameObject* button2 = new MenuButton(new LoaderParams(100, 300, 400, 100, "exitbutton"), s_exitFromMenu);

	//push them in the vector
	m_gameObjects.push_back(button1);
	m_gameObjects.push_back(button2);

	std::cout << "entering MenuState\n";
	return true;
}

bool MenuState::onExit()
{
	//iterate through all the objects and clean
	for (int i = 0; i < (int)m_gameObjects.size(); ++i)
	{
		m_gameObjects[i]->clean();
	}
	m_gameObjects.clear();

	//remove the button textures
	TheTextureManager::Instance()->clearFromTextureMap("playbutton");
	TheTextureManager::Instance()->clearFromTextureMap("exitbutton");

	std::cout << "exiting MenuState\n";
	return true;
}<|endoftext|>


<commit_before>// MolarMass.cpp
// This program should loop through the csv file Atomic Masses.csv and sum the masses of elements

#include <fstream>
#include <iostream>
#include <string>

int main()
{

	double runningSum = 0, atmMass;
	std::string identifier; //Make this value the value from the SMILE
	identifier = 'H';
	std::string symbol, name, atmNum, str;
	std::ifstream elementInfo("atomicmasses.txt"); //Specifying file to be opened as variable elementInfo
	//elementInfo.open("");

	if (!elementInfo)
	{
		std::cout << "Error reading file\n";
		getline(std::cin, str);
		std::cin.get();
		exit(1);
	}

	while (!elementInfo.eof())
	{

		getline(elementInfo, symbol, ',');

		//getline(elementInfo, name, ',');
		//getline(elementInfo, atmNum, ' ');

		if (symbol == identifier)
		{
			elementInfo >> atmMass;
			runningSum += atmMass;
		}
	}

	elementInfo.close();
	std::cin.get();
}
<commit_msg>Update MolarMass.cpp<commit_after>// MolarMass.cpp
// This program should loop through the csv file Atomic Masses.csv and sum the masses of elements

#include <fstream>
#include <iostream>
#include <string>

int main()
{

    double runningSum = 0, atmMass;
    std::string identifier; //Make this value the value from the SMILE
    identifier = 'H';
    std::string symbol, name, atmNum, str;
    std::ifstream elementInfo; //Specifying file to be opened as variable elementInfo
    elementInfo.open("AtomicMasses");

    if (!elementInfo)
    {
        std::cout << "Error reading file\n";
        getline(std::cin, str);
        std::cin.get();
        exit(1);
    }

    while (!elementInfo.eof())
    {
        getline(elementInfo, str, '\n');

        elementInfo >> symbol;
        //getline(elementInfo, name, ',');
        //getline(elementInfo, atmNum, ' ');

        if (symbol == identifier)
        {
            std::cout << "hi";
            elementInfo >> atmMass;
            runningSum += atmMass;
        }
    }
    std::cout << runningSum;

    elementInfo.close();
}
<|endoftext|>


<commit_before>// Copyright 2015 Las Venturas Playground. All rights reserved.
// Use of this source code is governed by the MIT license, a copy of which can
// be found in the LICENSE file.

#include "plugin/native_function_manager.h"

#include <string.h>

#include "base/logging.h"
#include "plugin/fake_amx.h"
#include "plugin/sdk/amx.h"
#include "plugin/sdk/plugincommon.h"
#include "third_party/subhook/subhook.h"

namespace plugin {

namespace {

// Type definition of the original amx_Register_t function that is being intercepted.
typedef int AMXAPI(*amx_Register_t)(AMX* amx, const AMX_NATIVE_INFO* nativelist, int number);

// Global instance of the NativeFunctionManager instance, only to be used by amx_Register_hook().
NativeFunctionManager* g_native_function_manager = nullptr;

int amx_Register_hook(AMX* amx, const AMX_NATIVE_INFO* nativelist, int number) {
  return g_native_function_manager->OnRegister(amx, nativelist, number);
}

}  // namespace

NativeFunctionManager::NativeFunctionManager()
    : fake_amx_(new FakeAMX) {
  g_native_function_manager = this;
}

NativeFunctionManager::~NativeFunctionManager() {
  g_native_function_manager = nullptr;
}

bool NativeFunctionManager::Install() {
  if (!pAMXFunctions)
    return true;  // testing

  void* current_address = static_cast<void**>(pAMXFunctions)[PLUGIN_AMX_EXPORT_Register];
  if (current_address == nullptr) {
    LOG(ERROR) << "Invalid address found for the amx_Register() function.";
    return false;
  }

  hook_.reset(new SubHook(current_address, (void*) amx_Register_hook));
  if (!hook_->Install()) {
    LOG(ERROR) << "Unable to install a SubHook for the amx_Register() function.";
    return false;
  }

  return true;
}

int NativeFunctionManager::OnRegister(AMX* amx, const AMX_NATIVE_INFO* nativelist, int number) {
  if (nativelist != nullptr) {
    for (size_t index = 0; ; ++index) {
      if (nativelist[index].func == nullptr || nativelist[index].name == nullptr)
        break;

      const std::string native_name(nativelist[index].name);

      // The Pawn interpreter iterates over unresolved functions in the gamemode, and finds them in
      // the functions that are being registered in the amx_Register() call. This means that the first
      // function registered for a given name will be used, rather than having later registrations
      // override previous ones. Simply drop out if a double-registration is observed.
      if (!FunctionExists(native_name))
        native_functions_[native_name] = static_cast<NativeFn*>(nativelist[index].func);
    }
  }

  // Trampoline back to the original amx_Register function that we intercepted.
  return ((amx_Register_t) hook_->GetTrampoline())(amx, nativelist, number);
}

bool NativeFunctionManager::FunctionExists(const std::string& function_name) const {
  return native_functions_.find(function_name) != native_functions_.end();
}

int NativeFunctionManager::CallFunction(const std::string& function_name,
                                        const char* format, void** arguments) {
  auto function_iter = native_functions_.find(function_name);
  if (function_iter == native_functions_.end()) {
    LOG(WARNING) << "Attempting to invoke unknown Pawn native " << function_name << ". Ignoring.";
    return -1;
  }

  AMX* amx = fake_amx_->amx();

  size_t param_count = format ? strlen(format) : 0;

  params_.resize(param_count + 1);
  params_[0] = param_count * sizeof(cell);

  // Early-return if there are no arguments required for this native invication.
  if (!param_count)
    return function_iter->second(amx, params_.data());

  size_t arraySizeParamOffset = 1;

  // The CreateDynamicObjectEx method unfortunately follows a non-sensical parameter order, which
  // makes it different from every other method that accepts an array (or a string). Thank you.
  if (param_count == 15 && function_name == "CreateDynamicObjectEx")
    arraySizeParamOffset = 3;

  auto amx_stack = fake_amx_->GetScopedStackModifier();
  DCHECK(arguments);

  // Process the existing parameters, either store them in |params_| or push them on the stack.
  for (size_t i = 0; i < param_count; ++i) {
    switch (format[i]) {
    case 'i':
      params_[i + 1] = *reinterpret_cast<cell*>(arguments[i]);
      break;
    case 'f':
      params_[i + 1] = amx_ftoc(*reinterpret_cast<float*>(arguments[i]));
      break;
    case 'r':
      params_[i + 1] = amx_stack.PushCell(*reinterpret_cast<cell*>(arguments[i]));
      break;
    case 's':
      params_[i + 1] = amx_stack.PushString(reinterpret_cast<char*>(arguments[i]));
      break;
    case 'a':
      if (format[i + arraySizeParamOffset] != 'i') {
        LOG(WARNING) << "Cannot invoke " << function_name << ": 'a' parameter must be followed by a 'i'.";
        return -1;
      }

      {
        int32_t size = *reinterpret_cast<int32_t*>(arguments[i + arraySizeParamOffset]);

        params_[i + 1] = amx_stack.PushArray(reinterpret_cast<cell*>(arguments[i]), size);
        if (arraySizeParamOffset == 1)
          params_[i + 1 + arraySizeParamOffset] = size;
      }

      if (arraySizeParamOffset == 1)
        ++i;

      break;
    }
  }

  const int return_value = function_iter->second(amx, params_.data());

  // Read back the values which may have been modified by the SA-MP server.
  for (size_t i = 0; i < param_count; ++i) {
    switch (format[i]) {
    case 'r':
      amx_stack.ReadCell(params_[i + 1], reinterpret_cast<cell*>(arguments[i]));
      break;
    case 'a':
      {
        char* data = reinterpret_cast<char*>(arguments[i]);
        int32_t size = *reinterpret_cast<int32_t*>(arguments[i + arraySizeParamOffset]);

        amx_stack.ReadArray(params_[i + 1], data, size);
      }
      break;
    }
  }

  return return_value;
}

}  // namespace plugin
<commit_msg>Update the playgroundjs plugin for the latest streamer plugin syntax<commit_after>// Copyright 2015 Las Venturas Playground. All rights reserved.
// Use of this source code is governed by the MIT license, a copy of which can
// be found in the LICENSE file.

#include "plugin/native_function_manager.h"

#include <string.h>

#include "base/logging.h"
#include "plugin/fake_amx.h"
#include "plugin/sdk/amx.h"
#include "plugin/sdk/plugincommon.h"
#include "third_party/subhook/subhook.h"

namespace plugin {

namespace {

// Type definition of the original amx_Register_t function that is being intercepted.
typedef int AMXAPI(*amx_Register_t)(AMX* amx, const AMX_NATIVE_INFO* nativelist, int number);

// Global instance of the NativeFunctionManager instance, only to be used by amx_Register_hook().
NativeFunctionManager* g_native_function_manager = nullptr;

int amx_Register_hook(AMX* amx, const AMX_NATIVE_INFO* nativelist, int number) {
  return g_native_function_manager->OnRegister(amx, nativelist, number);
}

}  // namespace

NativeFunctionManager::NativeFunctionManager()
    : fake_amx_(new FakeAMX) {
  g_native_function_manager = this;
}

NativeFunctionManager::~NativeFunctionManager() {
  g_native_function_manager = nullptr;
}

bool NativeFunctionManager::Install() {
  if (!pAMXFunctions)
    return true;  // testing

  void* current_address = static_cast<void**>(pAMXFunctions)[PLUGIN_AMX_EXPORT_Register];
  if (current_address == nullptr) {
    LOG(ERROR) << "Invalid address found for the amx_Register() function.";
    return false;
  }

  hook_.reset(new SubHook(current_address, (void*) amx_Register_hook));
  if (!hook_->Install()) {
    LOG(ERROR) << "Unable to install a SubHook for the amx_Register() function.";
    return false;
  }

  return true;
}

int NativeFunctionManager::OnRegister(AMX* amx, const AMX_NATIVE_INFO* nativelist, int number) {
  if (nativelist != nullptr) {
    for (size_t index = 0; ; ++index) {
      if (nativelist[index].func == nullptr || nativelist[index].name == nullptr)
        break;

      const std::string native_name(nativelist[index].name);

      // The Pawn interpreter iterates over unresolved functions in the gamemode, and finds them in
      // the functions that are being registered in the amx_Register() call. This means that the first
      // function registered for a given name will be used, rather than having later registrations
      // override previous ones. Simply drop out if a double-registration is observed.
      if (!FunctionExists(native_name))
        native_functions_[native_name] = static_cast<NativeFn*>(nativelist[index].func);
    }
  }

  // Trampoline back to the original amx_Register function that we intercepted.
  return ((amx_Register_t) hook_->GetTrampoline())(amx, nativelist, number);
}

bool NativeFunctionManager::FunctionExists(const std::string& function_name) const {
  return native_functions_.find(function_name) != native_functions_.end();
}

int NativeFunctionManager::CallFunction(const std::string& function_name,
                                        const char* format, void** arguments) {
  auto function_iter = native_functions_.find(function_name);
  if (function_iter == native_functions_.end()) {
    LOG(WARNING) << "Attempting to invoke unknown Pawn native " << function_name << ". Ignoring.";
    return -1;
  }

  AMX* amx = fake_amx_->amx();

  size_t param_count = format ? strlen(format) : 0;

  params_.resize(param_count + 1);
  params_[0] = param_count * sizeof(cell);

  // Early-return if there are no arguments required for this native invication.
  if (!param_count)
    return function_iter->second(amx, params_.data());

  size_t arraySizeParamOffset = 1;

  // The CreateDynamicObjectEx method unfortunately follows a non-sensical parameter order, which
  // makes it different from every other method that accepts an array (or a string). Thank you.
  if (param_count == 17 && function_name == "CreateDynamicObjectEx")
    arraySizeParamOffset = 4;

  auto amx_stack = fake_amx_->GetScopedStackModifier();
  DCHECK(arguments);

  // Process the existing parameters, either store them in |params_| or push them on the stack.
  for (size_t i = 0; i < param_count; ++i) {
    switch (format[i]) {
    case 'i':
      params_[i + 1] = *reinterpret_cast<cell*>(arguments[i]);
      break;
    case 'f':
      params_[i + 1] = amx_ftoc(*reinterpret_cast<float*>(arguments[i]));
      break;
    case 'r':
      params_[i + 1] = amx_stack.PushCell(*reinterpret_cast<cell*>(arguments[i]));
      break;
    case 's':
      params_[i + 1] = amx_stack.PushString(reinterpret_cast<char*>(arguments[i]));
      break;
    case 'a':
      if (format[i + arraySizeParamOffset] != 'i') {
        LOG(WARNING) << "Cannot invoke " << function_name << ": 'a' parameter must be followed by a 'i'.";
        return -1;
      }

      {
        int32_t size = *reinterpret_cast<int32_t*>(arguments[i + arraySizeParamOffset]);

        params_[i + 1] = amx_stack.PushArray(reinterpret_cast<cell*>(arguments[i]), size);
        if (arraySizeParamOffset == 1)
          params_[i + 1 + arraySizeParamOffset] = size;
      }

      if (arraySizeParamOffset == 1)
        ++i;

      break;
    }
  }

  const int return_value = function_iter->second(amx, params_.data());

  // Read back the values which may have been modified by the SA-MP server.
  for (size_t i = 0; i < param_count; ++i) {
    switch (format[i]) {
    case 'r':
      amx_stack.ReadCell(params_[i + 1], reinterpret_cast<cell*>(arguments[i]));
      break;
    case 'a':
      {
        char* data = reinterpret_cast<char*>(arguments[i]);
        int32_t size = *reinterpret_cast<int32_t*>(arguments[i + arraySizeParamOffset]);

        amx_stack.ReadArray(params_[i + 1], data, size);
      }
      break;
    }
  }

  return return_value;
}

}  // namespace plugin
<|endoftext|>