<commit_before>#include "evaluator.hpp"
#include "action.hpp"
#include "expr.hpp"
#include "decode.hpp"
#include "type.hpp"
#include "decl.hpp"

#include <climits>
#include <sstream>
// testing only
#include <iostream>

namespace pip
{
  static cap::decoded_packet* create_modified_packet(const cap::packet& pkt)
  {
    cap::eth_header eth(pkt);

    // type == ipv4
    if(eth.ethertype = 0x800)
    {
      cap::ipv4_header ipv4(eth);

      // protocol == tcp
      if(ipv4.protocol == 6)
      {
	cap::tcp_header tcp(ipv4);
	return new cap::tcp_ipv4(eth, ipv4, tcp);
      }

    }

    throw std::runtime_error("Unsupported protocol.\n");
  }

  evaluator::evaluator(context& cxt, decl* prog, cap::packet& pkt)
    : cxt(cxt),
      prog(prog),
      data(pkt),
      arrival(pkt.timestamp()),
      ingress_port(),
      physical_port(),
      metadata(),
      keyreg(),
      decode()
  {
    assert(cap::ethernet_ethertype(pkt.data()) == 0x800  &&
	   "Non-ethernet frames are not supported.\n");
    assert(cap::ipv4_protocol(pkt.data()) == 0x06 &&
	   "Non-TCP packets are not supported.\n");

    modified_buffer = new unsigned char[pkt.size()];
    std::memcpy(modified_buffer, pkt.data(), pkt.size());

    modified_data = create_modified_packet(pkt);
    ingress_port = cap::tcp_src_port(pkt.data());

    // TODO: Perform static initialization. We need to evaluate all of
    // the table definitions to load them with their static rules.
    auto program = static_cast<program_decl*>(prog);
    std::vector<decl*> tables;
    for(auto declaration : program->decls)
      if(auto t = dynamic_cast<table_decl*>(declaration))
	tables.push_back(t);

    // TODO: Load the instructions from the first table.
    current_table = static_cast<table_decl*>(tables.front());
    for(auto a : current_table->prep)
      eval.push_back(a);
  }

  evaluator::~evaluator()
  {
    if(modified_buffer)
      delete[] modified_buffer;
  }

  const action*
  evaluator::fetch()
  {
    const action* a = eval.front();
    if(a)
      std::cout << "there is an action in eval\n";
    eval.pop_front();
    return a;
  }

  void
  evaluator::step()
  {
    // If the action queue is empty, proceed to egress processing. This
    // will execute the actions in the action list. If the action list
    // is empty, then the program is complete.
    if (eval.empty()) {
      if (actions.empty())
        return;

      // This marks the beginning of egress processing.
      eval.insert(eval.end(), actions.begin(), actions.end());
      actions.clear();
    }

    const action* a = fetch();
    switch (get_kind(a)) {
      case ak_advance:
        return eval_advance(cast<advance_action>(a));
      case ak_copy:
        return eval_copy(cast<copy_action>(a));
      case ak_set:
        return eval_set(cast<set_action>(a));
      case ak_write:
        return eval_write(cast<write_action>(a));
      case ak_clear:
        return eval_clear(cast<clear_action>(a));
      case ak_drop:
        return eval_drop(cast<drop_action>(a));
      case ak_match:
        return eval_match(cast<match_action>(a));
      case ak_goto:
        return eval_goto(cast<goto_action>(a));
      case ak_output:
        return eval_output(cast<output_action>(a));

      default:
        break;
    }

    throw std::logic_error("invalid action");
  }

  void
  evaluator::run()
  {
    while (!done())
      step();
  }

  void
  evaluator::eval_advance(const advance_action* a)
  {
    const auto n = static_cast<int_expr*>(a->amount);
    int amount = n->val;

    decode += amount;
  }

  // generate a bitmask over a specified range
  // credit to: ...
  template<typename R>
  static constexpr R
  bitfieldmask(unsigned int const a, unsigned int const b)
  {
    return ((static_cast<R>(-1) >> (((sizeof(R) * CHAR_BIT) - 1) - (b)))
	    & ~((1 << (a)) - 1));
  }

  // Copy n bits from position pos in a byte array into a 64-bit integer.
  static std::uint64_t
  data_to_key_reg(std::uint8_t* bytes, std::size_t pos, std::size_t n)
  {
    const std::uint8_t* current_byte = bytes + pos / CHAR_BIT;
    std::uint64_t reg = 0;

    std::size_t offset = pos % CHAR_BIT;

    // If the copy begins between two whole bytes,
    if(offset > 0) {
      // copy the specified bits.
      reg = *(current_byte++) & (0xff >> offset);

      // If there is nothing else to copy, shift off any excess and return.
      if(n <= CHAR_BIT - offset)
	return reg >> (CHAR_BIT - offset - n);
      // Remove the copied bits from n.
      else
	n -= CHAR_BIT - offset;
    }

    // Copy all remaining whole bytes.
    while(n >= CHAR_BIT) {
      reg = (reg << CHAR_BIT) + *(current_byte++);
      n -= CHAR_BIT;
    }

    // If some fraction of a byte remains to be copied, copy it.
    if(n > 0)
      reg = (reg << n) + (*current_byte >> (CHAR_BIT - n));

    return reg;
  }

  // Copy n bits from one byte array to another, starting at position pos.
  static void
  bitwise_copy(std::uint8_t* dst, std::uint8_t const * src,
	       std::size_t pos, std::size_t n)
  {
    std::uint8_t* current_byte = dst + (pos / CHAR_BIT);
    std::size_t i = 0;

    // if the copy begins in between two whole bytes
    if(pos % CHAR_BIT > 0) {
      // copy just what is needed
      *current_byte |= src[i] & (0xFF >> (pos % CHAR_BIT));

      // if there is nothing else to copy, terminate
      if(n <= CHAR_BIT - pos % CHAR_BIT)
	return;
      // otherwise, even out n to start on the next whole byte
      // (n is now a multiple of CHAR_BIT)
      else
	n -= CHAR_BIT - pos % CHAR_BIT;

      ++current_byte;
      // note that we have already accessed the first element of src
      ++i;
    }

    // copy all whole bytes that remain
    for(; n >= CHAR_BIT; n -= CHAR_BIT, ++i)
      *current_byte++ = src[i];

    // if there is a remainder of less than CHAR_BIT bits,
    // copy just that portion of the byte.
    if(n > 0) {
      *current_byte |= src[i] & (0xff << (CHAR_BIT - n));
    }
  }

  void
  evaluator::eval_copy(const copy_action* a)
  {
    auto src_loc = static_cast<offset_expr*>(a->src);
    auto dst_loc = static_cast<offset_expr*>(a->dst);
    auto n = a->n->val;

    auto src_pos = static_cast<int_expr*>(src_loc->pos);
    auto src_len = static_cast<int_expr*>(src_loc->len);
    auto dst_pos = static_cast<int_expr*>(dst_loc->pos);
    auto dst_len = static_cast<int_expr*>(dst_loc->len);

    if(*(src_loc->space) == "key")
      throw std::runtime_error("Cannot copy from a key register.\n");

    if(n > dst_len->val || n > src_len->val)
      throw std::runtime_error("Copy action overflows buffer.\n");

    if(src_len->val != dst_len->val)
      throw std::runtime_error
	("Length of copy source and destination must be equal.\n");

    if(*(dst_loc->space) == "key") {
      if(n > 64) {
	std::stringstream ss;
	ss << "Attempting to copy " << n << " bits into a register of size 64.\n";
	throw std::runtime_error(ss.str().c_str());
      }

      if(*(src_loc->space) == "packet") {
	keyreg = data_to_key_reg((std::uint8_t*)data.data(), src_pos->val, src_len->val);
      }

      else if(*(src_loc->space) == "header") {
	keyreg = data_to_key_reg((std::uint8_t*)data.data() + SIZE_ETHERNET, src_pos->val, src_len->val);
      }

      else if(*(src_loc->space) == "meta")
	throw std::runtime_error("Unimplemented.\n");
    }

    else if(*(dst_loc->space) == "header") {
      if(*(src_loc->space) == "packet") {
	if((dst_len->val + dst_pos->val) > (data.size() - SIZE_ETHERNET)) {
	  std::stringstream ss;
	  ss << "Cannot write beyond buffer. Attempting to copy ";
	  ss << dst_len->val;
	  ss << " bits at position ";
	  ss << dst_pos->val;
	  ss << " of a buffer of size ";
	  ss << data.size() - SIZE_ETHERNET;
	  throw std::runtime_error(ss.str().c_str());
	}

	bitwise_copy(modified_buffer + SIZE_ETHERNET, (std::uint8_t*)data.data(), dst_pos->val, dst_len->val);
      }
      else if(*(src_loc->space) == "meta")
	throw std::runtime_error("Unimplemented.\n");
    }
    else if(*(dst_loc->space) == "meta")
      return;
    else if(*(dst_loc->space) == "packet")
      if(*(src_loc->space) == "header") {
	if(dst_len->val + dst_pos->val > data.size()) {
	  std::stringstream ss;
	  ss << "Cannot write beyond buffer. Attempting to copy ";
	  ss << dst_len->val;
	  ss << " bits at position ";
	  ss << dst_pos->val;
	  ss << " of a buffer of size ";
	  ss << data.size();
	  throw std::runtime_error(ss.str().c_str());
	}

	bitwise_copy(modified_buffer, packet_header(data), dst_pos->val, dst_len->val);
      }
      return;

  }

  // Copy n bits from a uint64_t into a byte array at position pos.
  // See data_to_key_reg and bitwise_copy for further documentation.
  static void
  reg_to_buf(std::uint8_t* bytes, std::uint64_t in,
	     std::size_t pos, std::size_t n)
  {
    std::uint8_t* current_byte = bytes + pos / CHAR_BIT;
    std::size_t offset = pos % CHAR_BIT;

    if(offset > 0) {
      *current_byte++ = (std::uint8_t)in & (0xff >> offset);

      if(n <= CHAR_BIT - offset)
	return;
      else
	n -= CHAR_BIT - offset;

      in >>= offset;
    }

    while(n >= CHAR_BIT) {
      *current_byte++ = (std::uint8_t)in & 0xff;
      in >>= 8;
      n -= CHAR_BIT;
    }

    if(n > 0)
      *current_byte = (in << (CHAR_BIT - n)) + (*current_byte >> n);
  }

  void
  evaluator::eval_set(const set_action* a)
  {
    const auto loc = static_cast<offset_expr*>(a->f);
    const auto pos_expr = static_cast<int_expr*>(loc->pos);

    const auto val_expr = static_cast<int_expr*>(a->v);

    std::uint64_t value = val_expr->val;
    std::size_t val_width = static_cast<int_type*>(val_expr->ty)->width;
    std::size_t position = pos_expr->val;

    if(val_width + position > data.size()) {
      std::stringstream ss;
      ss << "Cannot write beyond buffer. Attempting to write a value of ";
      ss << val_width;
      ss << " bits at position ";
      ss << position;
      ss << " of a buffer of size ";
      ss << data.size();
      throw std::runtime_error(ss.str().c_str());
    }

    reg_to_buf(modified_buffer, value, position, val_width);
  }

  void
  evaluator::eval_write(const write_action* a)
  {
    eval.push_back(a);
  }

  void
  evaluator::eval_clear(const clear_action* a)
  {
    eval.clear();
  }

  void
  evaluator::eval_drop(const drop_action* a)
  {
    actions.clear();
  }

  void
  evaluator::eval_match(const match_action* a)
  {
    //TODO: Terminate after one match has been made?

    // If one of the rules matches the key register, then add
    // that rule's action list to the action list for egress processing.
    for(auto r : current_table->rules)
    {
      auto key = r->key;
      std::cout << "keyreg: " << std::hex << keyreg << '\n';

      switch(get_kind(key))
      {
      case ek_int:
      {
	std::cout << "int rule matched\n";
	auto key_val = static_cast<int_expr*>(key)->val;
	if(keyreg == key_val)
	  for(auto a : r->acts)
	    actions.push_back(a);
	break;
      }
      case ek_port:
      {
	std::cout << "port rule matched\n";
	auto key_val = static_cast<port_expr*>(key)->port_num;
	if(keyreg == key_val)
	  for(auto a : r->acts)
	    actions.push_back(a);
	break;
      }
      case ek_miss:
      {
	std::cout << "missed\n";
	for(auto a : r->acts)
	  actions.push_back(a);
	break;
      }
      default:
	throw std::runtime_error("Rule key type does not match table kind.\n");
	break;
      }
    }
  }

  void
  evaluator::eval_goto(const goto_action* a)
  {
    // goto is a terminator action; eval must be empty at this point for this
    // program to be well formed.
    assert(eval.empty());

    decl* dst = static_cast<ref_expr*>(a->dest)->ref;
    current_table = static_cast<table_decl*>(dst);

    for(auto a : current_table->prep)
      eval.push_back(a);
  }

  void
  evaluator::eval_output(const output_action* a)
  {
    const port_expr* port = static_cast<port_expr*>(a->port);

    modified_data->set_output_port(port->port_num);
  }

} // namespace pip
<commit_msg>Print something to standard output when an action is evaluated.<commit_after>#include "evaluator.hpp"
#include "action.hpp"
#include "expr.hpp"
#include "decode.hpp"
#include "type.hpp"
#include "decl.hpp"
#include "dumper.hpp"

#include <climits>
#include <sstream>
// testing only
#include <iostream>

namespace pip
{
  static cap::decoded_packet* create_modified_packet(const cap::packet& pkt)
  {
    cap::eth_header eth(pkt);

    // type == ipv4
    if(eth.ethertype = 0x800)
    {
      cap::ipv4_header ipv4(eth);

      // protocol == tcp
      if(ipv4.protocol == 6)
      {
	cap::tcp_header tcp(ipv4);
	return new cap::tcp_ipv4(eth, ipv4, tcp);
      }

    }

    throw std::runtime_error("Unsupported protocol.\n");
  }

  evaluator::evaluator(context& cxt, decl* prog, cap::packet& pkt)
    : cxt(cxt),
      prog(prog),
      data(pkt),
      arrival(pkt.timestamp()),
      ingress_port(),
      physical_port(),
      metadata(),
      keyreg(),
      decode()
  {
    assert(cap::ethernet_ethertype(pkt.data()) == 0x800  &&
	   "Non-ethernet frames are not supported.\n");
    assert(cap::ipv4_protocol(pkt.data()) == 0x06 &&
	   "Non-TCP packets are not supported.\n");

    modified_buffer = new unsigned char[pkt.size()];
    std::memcpy(modified_buffer, pkt.data(), pkt.size());

    modified_data = create_modified_packet(pkt);
    ingress_port = cap::tcp_src_port(pkt.data());

    // TODO: Perform static initialization. We need to evaluate all of
    // the table definitions to load them with their static rules.
    auto program = static_cast<program_decl*>(prog);
    std::vector<decl*> tables;
    for(auto declaration : program->decls)
      if(auto t = dynamic_cast<table_decl*>(declaration))
	tables.push_back(t);

    // TODO: Load the instructions from the first table.
    current_table = static_cast<table_decl*>(tables.front());
    for(auto a : current_table->prep)
      eval.push_back(a);
  }

  evaluator::~evaluator()
  {
    if(modified_buffer)
      delete[] modified_buffer;
  }

  const action*
  evaluator::fetch()
  {
    const action* a = eval.front();
    if(a)
      std::cout << "there is an action in eval\n";
    eval.pop_front();
    return a;
  }

  void
  evaluator::step()
  {
    // If the action queue is empty, proceed to egress processing. This
    // will execute the actions in the action list. If the action list
    // is empty, then the program is complete.
    if (eval.empty()) {
      if (actions.empty())
        return;

      // This marks the beginning of egress processing.
      eval.insert(eval.end(), actions.begin(), actions.end());
      actions.clear();
    }

    const action* a = fetch();
    switch (get_kind(a)) {
      case ak_advance:
        return eval_advance(cast<advance_action>(a));
      case ak_copy:
        return eval_copy(cast<copy_action>(a));
      case ak_set:
        return eval_set(cast<set_action>(a));
      case ak_write:
        return eval_write(cast<write_action>(a));
      case ak_clear:
        return eval_clear(cast<clear_action>(a));
      case ak_drop:
        return eval_drop(cast<drop_action>(a));
      case ak_match:
        return eval_match(cast<match_action>(a));
      case ak_goto:
        return eval_goto(cast<goto_action>(a));
      case ak_output:
        return eval_output(cast<output_action>(a));

      default:
        break;
    }

    throw std::logic_error("invalid action");
  }

  void
  evaluator::run()
  {
    while (!done())
      step();
  }

  void
  evaluator::eval_advance(const advance_action* a)
  {
    const auto n = static_cast<int_expr*>(a->amount);
    int amount = n->val;

    decode += amount;

    std::cout << "Decode: " << decode << '\n';
  }

  // Copy n bits from position pos in a byte array into a 64-bit integer.
  static std::uint64_t
  data_to_key_reg(std::uint8_t* bytes, std::size_t pos, std::size_t n)
  {
    const std::uint8_t* current_byte = bytes + pos / CHAR_BIT;
    std::uint64_t reg = 0;

    std::size_t offset = pos % CHAR_BIT;

    // If the copy begins between two whole bytes,
    if(offset > 0) {
      // copy the specified bits.
      reg = *(current_byte++) & (0xff >> offset);

      // If there is nothing else to copy, shift off any excess and return.
      if(n <= CHAR_BIT - offset)
	return reg >> (CHAR_BIT - offset - n);
      // Remove the copied bits from n.
      else
	n -= CHAR_BIT - offset;
    }

    // Copy all remaining whole bytes.
    while(n >= CHAR_BIT) {
      reg = (reg << CHAR_BIT) + *(current_byte++);
      n -= CHAR_BIT;
    }

    // If some fraction of a byte remains to be copied, copy it.
    if(n > 0)
      reg = (reg << n) + (*current_byte >> (CHAR_BIT - n));

    return reg;
  }

  // Copy n bits from one byte array to another, starting at position pos.
  static void
  bitwise_copy(std::uint8_t* dst, std::uint8_t const * src,
	       std::size_t pos, std::size_t n)
  {
    std::uint8_t* current_byte = dst + (pos / CHAR_BIT);
    std::size_t i = 0;

    // if the copy begins in between two whole bytes
    if(pos % CHAR_BIT > 0) {
      // copy just what is needed
      *current_byte |= src[i] & (0xFF >> (pos % CHAR_BIT));

      // if there is nothing else to copy, terminate
      if(n <= CHAR_BIT - pos % CHAR_BIT)
	return;
      // otherwise, even out n to start on the next whole byte
      // (n is now a multiple of CHAR_BIT)
      else
	n -= CHAR_BIT - pos % CHAR_BIT;

      ++current_byte;
      // note that we have already accessed the first element of src
      ++i;
    }

    // copy all whole bytes that remain
    for(; n >= CHAR_BIT; n -= CHAR_BIT, ++i)
      *current_byte++ = src[i];

    // if there is a remainder of less than CHAR_BIT bits,
    // copy just that portion of the byte.
    if(n > 0) {
      *current_byte |= src[i] & (0xff << (CHAR_BIT - n));
    }
  }

  void
  evaluator::eval_copy(const copy_action* a)
  {
    auto src_loc = static_cast<offset_expr*>(a->src);
    auto dst_loc = static_cast<offset_expr*>(a->dst);
    auto n = a->n->val;

    auto src_pos = static_cast<int_expr*>(src_loc->pos);
    auto src_len = static_cast<int_expr*>(src_loc->len);
    auto dst_pos = static_cast<int_expr*>(dst_loc->pos);
    auto dst_len = static_cast<int_expr*>(dst_loc->len);

    if(*(src_loc->space) == "key")
      throw std::runtime_error("Cannot copy from a key register.\n");

    if(n > dst_len->val || n > src_len->val)
      throw std::runtime_error("Copy action overflows buffer.\n");

    if(src_len->val != dst_len->val)
      throw std::runtime_error
	("Length of copy source and destination must be equal.\n");

    if(*(dst_loc->space) == "key") {
      if(n > 64) {
	std::stringstream ss;
	ss << "Attempting to copy " << n << " bits into a register of size 64.\n";
	throw std::runtime_error(ss.str().c_str());
      }

      if(*(src_loc->space) == "packet") {
	keyreg = data_to_key_reg((std::uint8_t*)data.data(), src_pos->val, src_len->val);
      }

      else if(*(src_loc->space) == "header") {
	keyreg = data_to_key_reg((std::uint8_t*)data.data() + SIZE_ETHERNET, src_pos->val, src_len->val);

      }

      else if(*(src_loc->space) == "meta")
	throw std::runtime_error("Unimplemented.\n");

      std::cout << "Copy " << n << " bits to key register. Register value: " << keyreg << '\n';
    }

    else if(*(dst_loc->space) == "header") {
      if(*(src_loc->space) == "packet") {
	if((dst_len->val + dst_pos->val) > (data.size() - SIZE_ETHERNET)) {
	  std::stringstream ss;
	  ss << "Cannot write beyond buffer. Attempting to copy ";
	  ss << dst_len->val;
	  ss << " bits at position ";
	  ss << dst_pos->val;
	  ss << " of a buffer of size ";
	  ss << data.size() - SIZE_ETHERNET;
	  throw std::runtime_error(ss.str().c_str());
	}

	bitwise_copy(modified_buffer + SIZE_ETHERNET, (std::uint8_t*)data.data(), dst_pos->val, dst_len->val);
      }
      else if(*(src_loc->space) == "meta")
	throw std::runtime_error("Unimplemented.\n");

      std::cout << "Copy " << dst_len->val << " bits at position " << dst_pos->val << " into header. Header value: (unimplemented)\n";
      // TODO: print(header);
    }
    else if(*(dst_loc->space) == "meta")
      return;
    else if(*(dst_loc->space) == "packet") {
      if(*(src_loc->space) == "header") {
	if(dst_len->val + dst_pos->val > data.size()) {
	  std::stringstream ss;
	  ss << "Cannot write beyond buffer. Attempting to copy ";
	  ss << dst_len->val;
	  ss << " bits at position ";
	  ss << dst_pos->val;
	  ss << " of a buffer of size ";
	  ss << data.size();
	  throw std::runtime_error(ss.str().c_str());
	}

	bitwise_copy(modified_buffer, packet_header(data), dst_pos->val, dst_len->val);
      }

      std::cout << "Copy " << dst_len->val << " bits at position " << dst_pos->val << " into packet. packet value: (unimplemented)\n";
      // TODO: print(packet);
    }
  }

  // Copy n bits from a uint64_t into a byte array at position pos.
  // See data_to_key_reg and bitwise_copy for further documentation.
  static void
  reg_to_buf(std::uint8_t* bytes, std::uint64_t in,
	     std::size_t pos, std::size_t n)
  {
    std::uint8_t* current_byte = bytes + pos / CHAR_BIT;
    std::size_t offset = pos % CHAR_BIT;

    if(offset > 0) {
      *current_byte++ = (std::uint8_t)in & (0xff >> offset);

      if(n <= CHAR_BIT - offset)
	return;
      else
	n -= CHAR_BIT - offset;

      in >>= offset;
    }

    while(n >= CHAR_BIT) {
      *current_byte++ = (std::uint8_t)in & 0xff;
      in >>= 8;
      n -= CHAR_BIT;
    }

    if(n > 0)
      *current_byte = (in << (CHAR_BIT - n)) + (*current_byte >> n);
  }

  void
  evaluator::eval_set(const set_action* a)
  {
    const auto loc = static_cast<offset_expr*>(a->f);
    const auto pos_expr = static_cast<int_expr*>(loc->pos);

    const auto val_expr = static_cast<int_expr*>(a->v);

    std::uint64_t value = val_expr->val;
    std::size_t val_width = static_cast<int_type*>(val_expr->ty)->width;
    std::size_t position = pos_expr->val;

    if(val_width + position > data.size()) {
      std::stringstream ss;
      ss << "Cannot write beyond buffer. Attempting to write a value of ";
      ss << val_width;
      ss << " bits at position ";
      ss << position;
      ss << " of a buffer of size ";
      ss << data.size();
      throw std::runtime_error(ss.str().c_str());
    }

    reg_to_buf(modified_buffer, value, position, val_width);

    std::cout << "Set " << val_width << " bits of packet to " << value << ". Value of packet: (unimplemented).\n";
  }

  void
  evaluator::eval_write(const write_action* a)
  {
    eval.push_back(a);
    std::cout << "Write action:\n";
    dumper d(std::cout);
    d(a);
  }

  void
  evaluator::eval_clear(const clear_action* a)
  {
    eval.clear();
    std::cout << "Clear.\n";
  }

  void
  evaluator::eval_drop(const drop_action* a)
  {
    actions.clear();
    std::cout << "Drop.\n";
  }

  void
  evaluator::eval_match(const match_action* a)
  {
    std::cout << "Match\n";
    //TODO: Terminate after one match has been made?

    // If one of the rules matches the key register, then add
    // that rule's action list to the action list for egress processing.
    for(auto r : current_table->rules)
    {
      auto key = r->key;
      std::cout << "keyreg: " << std::hex << keyreg << '\n';

      switch(get_kind(key))
      {
      case ek_int:
      {
	std::cout << "Integer matched\n";
	auto key_val = static_cast<int_expr*>(key)->val;
	if(keyreg == key_val)
	  for(auto a : r->acts)
	    actions.push_back(a);
	break;
      }
      case ek_port:
      {
	std::cout << "Port matched\n";
	auto key_val = static_cast<port_expr*>(key)->port_num;
	if(keyreg == key_val)
	  for(auto a : r->acts)
	    actions.push_back(a);
	break;
      }
      case ek_miss:
      {
	std::cout << "Packet missed\n";
	for(auto a : r->acts)
	  actions.push_back(a);
	break;
      }
      default:
	throw std::runtime_error("Rule key type does not match table kind.\n");
	break;
      }
    }
  }

  void
  evaluator::eval_goto(const goto_action* a)
  {
    // goto is a terminator action; eval must be empty at this point for this
    // program to be well formed.
    assert(eval.empty());

    decl* dst = static_cast<ref_expr*>(a->dest)->ref;
    current_table = static_cast<table_decl*>(dst);

    for(auto a : current_table->prep)
      eval.push_back(a);

    std::cout << "Goto table: " << current_table << '\n';
  }

  void
  evaluator::eval_output(const output_action* a)
  {
    const port_expr* port = static_cast<port_expr*>(a->port);

    modified_data->set_output_port(port->port_num);
    std::cout << "Output to port: " << port->port_num << '\n';
  }

} // namespace pip
<|endoftext|>


<commit_before>#include<d3d9.h>
#include "object.h"

void object::init(LPDIRECT3DDEVICE9 g_pd3dDevice, int VertexSize, int IndexSize, CUSTOMVERTEX Vertices[], WORD Indices[],int PrimitiveCount)
{

	m_VertexSize = VertexSize;
	m_IndexSize = IndexSize;
	m_PrimitiveCount = PrimitiveCount;
	g_pd3dDevice->CreateVertexBuffer(m_VertexSize*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &m_pVertexBuffer, NULL);
	g_pd3dDevice->CreateIndexBuffer(m_IndexSize*sizeof(WORD), 0, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &m_pIndexBuffer, NULL);
	WriteInVertexBuffer(Vertices);
	WriteInIndexBuffer(Indices);
}

object::~object()
{
	SAFE_RELEASE(m_pVertexBuffer);
	SAFE_RELEASE(m_pIndexBuffer);
}

object::object()
{
	m_VertexSize = 0;
	m_IndexSize = 0;
	m_PrimitiveCount = 0;
	m_pVertexBuffer = NULL;
	m_pIndexBuffer = NULL;
	mVertices = NULL;
	mIndices = NULL;
}

void object::WriteInVertexBuffer(CUSTOMVERTEX Vertices[])
{
	m_pVertexBuffer->Lock(0, sizeof(Vertices), (void**)&mVertices, 0);
	memcpy(mVertices, Vertices, sizeof(Vertices));
	m_pIndexBuffer->Unlock();
}

void object::WriteInIndexBuffer(WORD Indices[])
{
	m_pIndexBuffer->Lock(0, sizeof(Indices), (void**)&mIndices, 0);
	memcpy(mIndices, Indices, sizeof(Indices));
	m_pIndexBuffer->Unlock();
}

void object::ObjectPrint(LPDIRECT3DDEVICE9 g_pd3dDevice)
{
	g_pd3dDevice->SetStreamSource(0, m_pVertexBuffer, 0, sizeof(CUSTOMVERTEX));
	g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
	g_pd3dDevice->SetIndices(m_pIndexBuffer);
	g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, m_VertexSize, 0,(UINT)m_PrimitiveCount);
}<commit_msg>solve a bug<commit_after>#include<d3d9.h>
#include "object.h"

void object::init(LPDIRECT3DDEVICE9 g_pd3dDevice, int VertexSize, int IndexSize, CUSTOMVERTEX Vertices[], WORD Indices[],int PrimitiveCount)
{

	m_VertexSize = VertexSize;
	m_IndexSize = IndexSize;
	m_PrimitiveCount = PrimitiveCount;
	g_pd3dDevice->CreateVertexBuffer(m_VertexSize*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &m_pVertexBuffer, NULL);
	g_pd3dDevice->CreateIndexBuffer(m_IndexSize*sizeof(WORD), 0, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &m_pIndexBuffer, NULL);
	WriteInVertexBuffer(Vertices);
	WriteInIndexBuffer(Indices);
}

object::~object()
{
	SAFE_RELEASE(m_pVertexBuffer);
	SAFE_RELEASE(m_pIndexBuffer);
}

object::object()
{
	m_VertexSize = 0;
	m_IndexSize = 0;
	m_PrimitiveCount = 0;
	m_pVertexBuffer = NULL;
	m_pIndexBuffer = NULL;
	mVertices = NULL;
	mIndices = NULL;
}

void object::WriteInVertexBuffer(CUSTOMVERTEX Vertices[])
{
	m_pVertexBuffer->Lock(0, 0, (void**)&mVertices, 0);
	memcpy(mVertices, Vertices, m_VertexSize*sizeof(CUSTOMVERTEX));
	m_pIndexBuffer->Unlock();
}

void object::WriteInIndexBuffer(WORD Indices[])
{
	m_pIndexBuffer->Lock(0, 0, (void**)&mIndices, 0);
	memcpy(mIndices, Indices, m_IndexSize*sizeof(WORD));
	m_pIndexBuffer->Unlock();
}

void object::ObjectPrint(LPDIRECT3DDEVICE9 g_pd3dDevice)
{
	g_pd3dDevice->SetStreamSource(0, m_pVertexBuffer, 0, sizeof(CUSTOMVERTEX));
	g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
	g_pd3dDevice->SetIndices(m_pIndexBuffer);
	g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, m_VertexSize, 0,(UINT)m_PrimitiveCount);
}<|endoftext|>


<commit_before>/* Copyright 2013 - 2015 Yurii Litvinov and CyberTech Labs Ltd.
 *
 * 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. */

#include <QtCore/qglobal.h>

#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
	#include <QtGui/QApplication>
#else
	#include <QtWidgets/QApplication>
#endif

#include <QtCore/QTimer>
#include <QtCore/QCoreApplication>
#include <QtCore/QEventLoop>
#include <QtCore/QFileInfo>

#include <trikKernel/configurer.h>
#include <trikKernel/deinitializationHelper.h>
#include <trikKernel/fileUtils.h>
#include <trikKernel/applicationInitHelper.h>
#include <trikKernel/paths.h>
#include <trikControl/brickFactory.h>
#include <trikControl/brickInterface.h>
#include <trikScriptRunner/trikScriptRunner.h>
#include <trikScriptRunner/trikPythonRunner.h>
#include <trikNetwork/mailboxFactory.h>
#include <trikNetwork/mailboxInterface.h>

#include <QsLog.h>

int main(int argc, char *argv[])
{
	QStringList params;
	for (int i = 1; i < argc; ++i) {
		params << QString(argv[i]);
	}

	QScopedPointer<QCoreApplication> app;

	if (params.contains("--no-display") || params.contains("-no-display")) {
		app.reset(new QCoreApplication(argc, argv));
	} else {
		app.reset(new QApplication(argc, argv));
	}

	app->setApplicationName("TrikRun");

	// RAII-style code to ensure that after brick gets destroyed there will be an event loop that cleans it up.
	trikKernel::DeinitializationHelper helper;
	Q_UNUSED(helper);

	trikKernel::ApplicationInitHelper initHelper(*app);

	initHelper.commandLineParser().addPositionalArgument("file", QObject::tr("File with script to execute")
            + " " + QObject::tr("(optional of -js or -py option is specified)"));

    initHelper.commandLineParser().addOption("js", "js-script"
            , QObject::tr("JavaScript Script to be executed directly from command line.") + "\n"
                    + QObject::tr("\tExample: ./trikRun -js \"brick.smile(); script.wait(2000);\""));

    initHelper.commandLineParser().addOption("py", "py-script"
            , QObject::tr("Python Script to be executed directly from command line.") + "\n"
                    + QObject::tr("\tExample: ./trikRun -py \"brick.smile(); script.wait(2000);\"")); // FIXME: example

	initHelper.commandLineParser().addFlag("no-display", "no-display"
			, QObject::tr("Disable display support. When this flag is active, trikRun can work without QWS or even "
						  "physical display"));

    initHelper.commandLineParser().addApplicationDescription(QObject::tr("Runner of JavaScript and Python files."));

	if (!initHelper.parseCommandLine()) {
		return 0;
	}

	initHelper.init();

	QLOG_INFO() << "TrikRun started";

    enum ScriptType {
        JAVASCRIPT,
        PYTHON
    };

    const auto run = [&](const QString &script, ScriptType stype) {
		QScopedPointer<trikControl::BrickInterface> brick(
				trikControl::BrickFactory::create(initHelper.configPath(), trikKernel::Paths::mediaPath())
				);

		trikKernel::Configurer configurer(initHelper.configPath() + "/system-config.xml"
				, initHelper.configPath() + "/model-config.xml");

		QScopedPointer<trikNetwork::MailboxInterface> mailbox(trikNetwork::MailboxFactory::create(configurer));

        trikScriptRunner::TrikScriptRunnerInterface * result;
        switch (stype) {
            case JAVASCRIPT:
                result = new trikScriptRunner::TrikScriptRunner(*brick, mailbox.data());
                break;
            case PYTHON:
                result = new trikScriptRunner::TrikPythonRunner(*brick, mailbox.data());
                break;
            default:
                QLOG_ERROR() << "No such script engine";
                return 1;
        }

        QObject::connect(result, SIGNAL(completed(QString, int)), app.data(), SLOT(quit()));
        result->run(script);
		return app->exec();
	};

    if (initHelper.commandLineParser().isSet("js")) {
        return run(initHelper.commandLineParser().value("js"), JAVASCRIPT);
    } else if (initHelper.commandLineParser().isSet("py")) {
        return run(initHelper.commandLineParser().value("py"), PYTHON);
    } else {
		const QStringList positionalArgs = initHelper.commandLineParser().positionalArgs();
		if (positionalArgs.size() == 1) {
            const QFileInfo fileInfo(positionalArgs[0]);
            ScriptType stype;

            if (fileInfo.suffix() == "js" || fileInfo.suffix() == "qts") {
                stype = JAVASCRIPT;
            } else if (fileInfo.suffix() == "py") {
                stype = PYTHON;
            } else {
                QLOG_ERROR() << "No such script engine";
                return 1;
            }
            return run(trikKernel::FileUtils::readFromFile(positionalArgs[0]), stype);
		} else {
			initHelper.commandLineParser().showHelp();
			return 1;
		}
	}
}
<commit_msg>fix includes<commit_after>/* Copyright 2013 - 2015 Yurii Litvinov and CyberTech Labs Ltd.
 *
 * 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. */

#include <QtCore/qglobal.h>

#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
	#include <QtGui/QApplication>
#else
	#include <QtWidgets/QApplication>
#endif

#include <QtCore/QTimer>
#include <QtCore/QCoreApplication>
#include <QtCore/QEventLoop>
#include <QtCore/QFileInfo>

#include <trikKernel/configurer.h>
#include <trikKernel/deinitializationHelper.h>
#include <trikKernel/fileUtils.h>
#include <trikKernel/applicationInitHelper.h>
#include <trikKernel/paths.h>
#include <trikControl/brickFactory.h>
#include <trikControl/brickInterface.h>
#include "trikScriptRunner/trikScriptRunnerInterface.h"
#include "trikScriptRunner/trikScriptRunner.h"
#include "trikScriptRunner/trikPythonRunner.h"
#include <trikNetwork/mailboxFactory.h>
#include <trikNetwork/mailboxInterface.h>

#include <QsLog.h>

int main(int argc, char *argv[])
{
	QStringList params;
	for (int i = 1; i < argc; ++i) {
		params << QString(argv[i]);
	}

	QScopedPointer<QCoreApplication> app;

	if (params.contains("--no-display") || params.contains("-no-display")) {
		app.reset(new QCoreApplication(argc, argv));
	} else {
		app.reset(new QApplication(argc, argv));
	}

	app->setApplicationName("TrikRun");

	// RAII-style code to ensure that after brick gets destroyed there will be an event loop that cleans it up.
	trikKernel::DeinitializationHelper helper;
	Q_UNUSED(helper);

	trikKernel::ApplicationInitHelper initHelper(*app);

	initHelper.commandLineParser().addPositionalArgument("file", QObject::tr("File with script to execute")
            + " " + QObject::tr("(optional of -js or -py option is specified)"));

    initHelper.commandLineParser().addOption("js", "js-script"
            , QObject::tr("JavaScript Script to be executed directly from command line.") + "\n"
                    + QObject::tr("\tExample: ./trikRun -js \"brick.smile(); script.wait(2000);\""));

    initHelper.commandLineParser().addOption("py", "py-script"
            , QObject::tr("Python Script to be executed directly from command line.") + "\n"
                    + QObject::tr("\tExample: ./trikRun -py \"brick.smile(); script.wait(2000);\"")); // FIXME: example

	initHelper.commandLineParser().addFlag("no-display", "no-display"
			, QObject::tr("Disable display support. When this flag is active, trikRun can work without QWS or even "
						  "physical display"));

    initHelper.commandLineParser().addApplicationDescription(QObject::tr("Runner of JavaScript and Python files."));

	if (!initHelper.parseCommandLine()) {
		return 0;
	}

	initHelper.init();

	QLOG_INFO() << "TrikRun started";

    enum ScriptType {
        JAVASCRIPT,
        PYTHON
    };

    const auto run = [&](const QString &script, ScriptType stype) {
		QScopedPointer<trikControl::BrickInterface> brick(
				trikControl::BrickFactory::create(initHelper.configPath(), trikKernel::Paths::mediaPath())
				);

		trikKernel::Configurer configurer(initHelper.configPath() + "/system-config.xml"
				, initHelper.configPath() + "/model-config.xml");

		QScopedPointer<trikNetwork::MailboxInterface> mailbox(trikNetwork::MailboxFactory::create(configurer));

        trikScriptRunner::TrikScriptRunnerInterface * result;
        switch (stype) {
            case JAVASCRIPT:
                result = new trikScriptRunner::TrikScriptRunner(*brick, mailbox.data());
                break;
            case PYTHON:
                result = new trikScriptRunner::TrikPythonRunner(*brick, mailbox.data());
                break;
            default:
                QLOG_ERROR() << "No such script engine";
                return 1;
        }

        QObject::connect(result, SIGNAL(completed(QString, int)), app.data(), SLOT(quit()));
        result->run(script);
		return app->exec();
	};

    if (initHelper.commandLineParser().isSet("js")) {
        return run(initHelper.commandLineParser().value("js"), JAVASCRIPT);
    } else if (initHelper.commandLineParser().isSet("py")) {
        return run(initHelper.commandLineParser().value("py"), PYTHON);
    } else {
		const QStringList positionalArgs = initHelper.commandLineParser().positionalArgs();
		if (positionalArgs.size() == 1) {
            const QFileInfo fileInfo(positionalArgs[0]);
            ScriptType stype;

            if (fileInfo.suffix() == "js" || fileInfo.suffix() == "qts") {
                stype = JAVASCRIPT;
            } else if (fileInfo.suffix() == "py") {
                stype = PYTHON;
            } else {
                QLOG_ERROR() << "No such script engine";
                return 1;
            }
            return run(trikKernel::FileUtils::readFromFile(positionalArgs[0]), stype);
		} else {
			initHelper.commandLineParser().showHelp();
			return 1;
		}
	}
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2006 Funambol
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/** @cond API */
/** @addtogroup ClientTest */
/** @{ */

#include "spdm/DeviceManagementNode.h"
#include "client/RawFileSyncSource.h"
#include "spds/spdsutils.h"
#include "client/DMTClientConfig.h"
#include "client/SyncClient.h"
#include "test/ClientTest.h"
#include "base/test.h"

#include <string>
#include <vector>
#include <iomanip>
#include <memory>

#ifdef WIN32
#include <direct.h>
#endif
#include <sys/stat.h>

#ifdef ENABLE_INTEGRATION_TESTS

/**
 * This code uses the ClientTest and RawFileSyncSource to test real
 * synchronization against a server. More than one RawFileSyncSource can
 * be active at once and each of them may (but does not have to be)
 * used for different kinds of data. The name of the source determines
 * which data is stored in it: it must be something supported by the
 * ClientTest class, because that is where the test data comes from.
 *
 * At least the following kinds of data are currently supported by the
 * ClientTest class (see ClientTest::getTestData() for more
 * information):
 * - vcard30 = vCard 3.0 contacts
 * - vcard21 = vCard 2.1 contacts
 * - ical20 = iCalendar 2.0 calendar events
 * - vcal10 = vCalendar 1.0 calendar events
 * - itodo20 = iCalendar 2.0 tasks
 *
 * Configuration is done by environment variables which indicate which
 * part below the root node "client-test" of the the configuration tree to use;
 * beyond that everything needed for synchronization is read from the
 * configuration tree.
 *
 * - CLIENT_TEST_SERVER = maps to name of root node in configuration tree
 * - CLIENT_TEST_SOURCES = comma separated list of active sources,
 *                         names as listed above
 * - CLIENT_TEST_DELAY = number of seconds to sleep between syncs, required
 *                       by some servers
 * - CLIENT_TEST_LOG = logfile name of a server, can be empty:
 *                     if given, then the content of that file will be
 *                     copied and stored together with the client log
 *                     (only works on Unix)
 *
 * For example, on Linux running
 * @verbatim
CLIENT_TEST_SERVER=funambol CLIENT_TEST_SOURCES=vcard21,vcal10 ./client-test
@endverbatim
 *
 * expects the following configuration layout:
 * @verbatim
~/.sync4j/client-test/
                      funambol_1/spds/
                                      syncml/config.text
                                      sources/
                                              vcard21/config.txt
                                              vcal10/config.txt
                      funambol_1/spds/
                                      <same as for funambol_1>
@endverbatim
 *
 * If any of the configuration nodes does not exist yet, then it will
 * be created, but further information may have to be added, in
 * particular:
 * - server URL
 * - server user name, password
 * - sources uri
 *
 * The two configurations are used to simulate synchronization between
 * two different clients.
 *
 * The file sources will store their items in sub directories of
 * a "client-data" directory created in the current working directory.
 *
 * Here is an example of using the CLIENT_TEST_LOG:
 * @verbatim
CLIENT_TEST_SERVER=funambol \
CLIENT_TEST_LOG=/opt/Funambol-3.0/ds-server/logs/funambol_ds.log \
CLIENT_TEST_SOURCES=vcard21 \
   ./client-test
@endverbatim
 *
 * will create files with the suffix .client.A.log for synchronizations with
 * the first client and .client.B.log for the second client. The base name
 * of these files is unique, so the corresponding part of the server log
 * is stored with the same base name and .server.log as suffix.
 */
class TestFileSource : public ClientTest {
public:
    TestFileSource(const std::string &id) :
        ClientTest(getenv("CLIENT_TEST_DELAY") ? atoi(getenv("CLIENT_TEST_DELAY")) : 0,
                   getenv("CLIENT_TEST_LOG") ? getenv("CLIENT_TEST_LOG") : ""),
        clientID(id) {
        const char *sourcelist = getenv("CLIENT_TEST_SOURCES");
        const char *server = getenv("CLIENT_TEST_SERVER");

        /* set up source list */
        if (!sourcelist) {
            sourcelist = "";
        }
        const char *eostr = strchr(sourcelist, ',');
        const char *start = sourcelist;

        while (eostr) {
            sources.push_back(std::string(start, 0, eostr - start));
            start = eostr + 1;
            eostr = strchr(start, ',');
        }
        if (start[0]) {
            sources.push_back(start);
        }

        /* check server */
        if (!server) {
            server = "funambol";
        }

        // get configuration and set obligatory fields
        LOG.setLevel(LOG_LEVEL_DEBUG);
        std::string root = std::string("client-test/") + server + "_" + id;
        config.reset(new DMTClientConfig(root.c_str()));
        config->read();
        DeviceConfig &dc(config->getDeviceConfig());
        if (!strlen(dc.getDevID())) {
            // no configuration yet
            config->setClientDefaults();
            dc.setDevID(id == "A" ? "sc-api-nat" : "sc-pim-ppc");
        }
        for (int source = 0; source < (int)sources.size(); source++) {
            ClientTest::Config testconfig;
            getSourceConfig(source, testconfig);
            CPPUNIT_ASSERT(testconfig.type);

            SyncSourceConfig* sc = config->getSyncSourceConfig(sources[source].c_str());
            if (!sc) {
                // no configuration yet
                config->setSourceDefaults(sources[source].c_str());
                sc = config->getSyncSourceConfig(sources[source].c_str());
                sc->setURI(testconfig.uri);
                CPPUNIT_ASSERT(sc);
            }

            sc->setType(testconfig.type);
        }
        config->save();
        config->open();

        if (id == "A") {
            /* we are the primary client, create a second one */
            clientB.reset(new TestFileSource("B"));
        }
    }

    virtual int getNumSources() {
        return (int)sources.size();
    }

    virtual void getSourceConfig(int source, Config &config) {
        memset(&config, 0, sizeof(config));

        getTestData(sources[source].c_str(), config);
        config.createSourceA =
            config.createSourceB = createSource;
    }

    virtual ClientTest* getClientB() {
        return clientB.get();
    }

    virtual bool isB64Enabled() {
        return false;
    }

    virtual int sync(
        const int *activeSources,
        SyncMode syncMode,
        const CheckSyncReport &checkReport,
        long maxMsgSize,
        long maxObjSize,
        bool loSupport,
        const char *encoding = 0) {
        SyncSource **syncSources = new SyncSource *[sources.size() + 1];
        int index, numsources = 0;
        memset(syncSources, 0, sizeof(syncSources[0]) * (sources.size() + 1));

        for (index = 0; activeSources[index] >= 0 && index < (int)sources.size(); index++) {
            // rewrite configuration as needed for test
            SyncSourceConfig *sourceConfig = config->getSyncSourceConfig(sources[activeSources[index]].c_str());
            CPPUNIT_ASSERT(sourceConfig);
            sourceConfig->setSync(syncModeKeyword(syncMode));
            sourceConfig->setEncoding(encoding);
            config->getAccessConfig().setMaxMsgSize(maxMsgSize);
            config->getDeviceConfig().setMaxObjSize(maxObjSize);
            config->getDeviceConfig().setLoSupport(loSupport);

            // create sync source using the third change tracking for syncs
            syncSources[numsources++] = createSource(activeSources[index], "S");
        }

        SyncClient client;
        int res = client.sync(*config, syncSources);
        CPPUNIT_ASSERT(client.getSyncReport());

        for (int source = 0; syncSources[source]; source++) {
            delete syncSources[source];
        }
        checkReport.check(res, *client.getSyncReport());
        return res;
    }

private:
    /** either "A" or "B" for first respectively second client */
    std::string clientID;

    /** only in "A": pointer to second client */
    std::auto_ptr<TestFileSource> clientB;

    /** vector of enabled sync sources, identified by a name which SyncClient::getConfig() supports */
    std::vector<std::string> sources;

    /** configuration tree itself */
    std::auto_ptr<DMTClientConfig> config;

    static SyncSource *createSource(ClientTest &client, int source, bool isSourceA) {
        // hand work over to real member function
        return ((TestFileSource &)client).createSource(source, isSourceA ? "A" : "B");
    }

    SyncSource *createSource(int source, const char *trackingSuffix) {
        class RawFileSyncSourceWithReport : public RawFileSyncSource {
        public:
            RawFileSyncSourceWithReport(const char* nodeName, const WCHAR* name, SyncSourceConfig* sc) :
                RawFileSyncSource(name, sc),
                fileNode(nodeName) {
                setReport(&report);
                setFileNode(&fileNode);
                /*
                 * Keeping track if changes is done via time() with a resolution of seconds.
                 * Sleep a bit to ensure that enough time passes.
                 */
#ifdef WIN32
				Sleep(1000);
#else
                sleep(1);
#endif
            }
        private:
            SyncSourceReport report;
            DeviceManagementNode fileNode;
        };

        CPPUNIT_ASSERT(source < (int)sources.size());
        ManagementNode *sourceNode = config->getSyncSourceNode(sources[source].c_str());
        CPPUNIT_ASSERT(sourceNode);
        char *fullName = sourceNode->createFullName();
        std::string nodeName = std::string(fullName) + "/changes_" + trackingSuffix;
        std::string dirName = sources[source] + "_" + clientID;
		WCHAR *name = toWideChar(sources[source].c_str());
        delete [] fullName;
        FileSyncSource *ss = new RawFileSyncSourceWithReport(
            nodeName.c_str(),
            name,
            config->getSyncSourceConfig(sources[source].c_str()));
		delete [] name;
#ifdef WIN32
        _mkdir(dirName.c_str());
#else
		mkdir(dirName.c_str(), S_IRWXU);
#endif
        ss->setDir(dirName.c_str());

        return ss;
    }
};

/**
 * the only purpose of this class is to own the first TestFileSource
 * and to register its tests at program startup
 */
static class RegisterTest {
public:
    RegisterTest() :
        testFileSource("A") {
        testFileSource.registerTests();
    }

private:
    TestFileSource testFileSource;
} registerTest;

/** @} */
/** @endcond */
#endif // ENABLE_INTEGRATION_TESTS
<commit_msg>added remark about usage of CLIENT_TEST_SERVER in synccompare.pl<commit_after>/*
 * Copyright (C) 2006 Funambol
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/** @cond API */
/** @addtogroup ClientTest */
/** @{ */

#include "spdm/DeviceManagementNode.h"
#include "client/RawFileSyncSource.h"
#include "spds/spdsutils.h"
#include "client/DMTClientConfig.h"
#include "client/SyncClient.h"
#include "test/ClientTest.h"
#include "base/test.h"

#include <string>
#include <vector>
#include <iomanip>
#include <memory>

#ifdef WIN32
#include <direct.h>
#endif
#include <sys/stat.h>

#ifdef ENABLE_INTEGRATION_TESTS

/**
 * This code uses the ClientTest and RawFileSyncSource to test real
 * synchronization against a server. More than one RawFileSyncSource can
 * be active at once and each of them may (but does not have to be)
 * used for different kinds of data. The name of the source determines
 * which data is stored in it: it must be something supported by the
 * ClientTest class, because that is where the test data comes from.
 *
 * At least the following kinds of data are currently supported by the
 * ClientTest class (see ClientTest::getTestData() for more
 * information):
 * - vcard30 = vCard 3.0 contacts
 * - vcard21 = vCard 2.1 contacts
 * - ical20 = iCalendar 2.0 calendar events
 * - vcal10 = vCalendar 1.0 calendar events
 * - itodo20 = iCalendar 2.0 tasks
 *
 * Configuration is done by environment variables which indicate which
 * part below the root node "client-test" of the the configuration tree to use;
 * beyond that everything needed for synchronization is read from the
 * configuration tree.
 *
 * - CLIENT_TEST_SERVER = maps to name of root node in configuration tree
 * - CLIENT_TEST_SOURCES = comma separated list of active sources,
 *                         names as listed above
 * - CLIENT_TEST_DELAY = number of seconds to sleep between syncs, required
 *                       by some servers
 * - CLIENT_TEST_LOG = logfile name of a server, can be empty:
 *                     if given, then the content of that file will be
 *                     copied and stored together with the client log
 *                     (only works on Unix)
 *
 * For example, on Linux running
 * @verbatim
CLIENT_TEST_SERVER=funambol CLIENT_TEST_SOURCES=vcard21,vcal10 ./client-test
@endverbatim
 *
 * expects the following configuration layout:
 * @verbatim
~/.sync4j/client-test/
                      funambol_1/spds/
                                      syncml/config.text
                                      sources/
                                              vcard21/config.txt
                                              vcal10/config.txt
                      funambol_1/spds/
                                      <same as for funambol_1>
@endverbatim
 *
 * If any of the configuration nodes does not exist yet, then it will
 * be created, but further information may have to be added, in
 * particular:
 * - server URL
 * - server user name, password
 * - sources uri
 *
 * The CLIENT_TEST_SERVER also has another meaning: it is used as hint
 * by the synccompare.pl script and causes it to automatically ignore
 * known, acceptable data modifications caused by sending an item to
 * a server and back again. Currently the script recognizes "funambol",
 * "scheduleworld", "synthesis" and "egroupware" as special server
 * names.
 *
 * The two configurations are used to simulate synchronization between
 * two different clients.
 *
 * The file sources will store their items in sub directories of
 * a "client-data" directory created in the current working directory.
 *
 * Here is an example of using the CLIENT_TEST_LOG:
 * @verbatim
CLIENT_TEST_SERVER=funambol \
CLIENT_TEST_LOG=/opt/Funambol-3.0/ds-server/logs/funambol_ds.log \
CLIENT_TEST_SOURCES=vcard21 \
   ./client-test
@endverbatim
 *
 * will create files with the suffix .client.A.log for synchronizations with
 * the first client and .client.B.log for the second client. The base name
 * of these files is unique, so the corresponding part of the server log
 * is stored with the same base name and .server.log as suffix.
 */
class TestFileSource : public ClientTest {
public:
    TestFileSource(const std::string &id) :
        ClientTest(getenv("CLIENT_TEST_DELAY") ? atoi(getenv("CLIENT_TEST_DELAY")) : 0,
                   getenv("CLIENT_TEST_LOG") ? getenv("CLIENT_TEST_LOG") : ""),
        clientID(id) {
        const char *sourcelist = getenv("CLIENT_TEST_SOURCES");
        const char *server = getenv("CLIENT_TEST_SERVER");

        /* set up source list */
        if (!sourcelist) {
            sourcelist = "";
        }
        const char *eostr = strchr(sourcelist, ',');
        const char *start = sourcelist;

        while (eostr) {
            sources.push_back(std::string(start, 0, eostr - start));
            start = eostr + 1;
            eostr = strchr(start, ',');
        }
        if (start[0]) {
            sources.push_back(start);
        }

        /* check server */
        if (!server) {
            server = "funambol";
        }

        // get configuration and set obligatory fields
        LOG.setLevel(LOG_LEVEL_DEBUG);
        std::string root = std::string("client-test/") + server + "_" + id;
        config.reset(new DMTClientConfig(root.c_str()));
        config->read();
        DeviceConfig &dc(config->getDeviceConfig());
        if (!strlen(dc.getDevID())) {
            // no configuration yet
            config->setClientDefaults();
            dc.setDevID(id == "A" ? "sc-api-nat" : "sc-pim-ppc");
        }
        for (int source = 0; source < (int)sources.size(); source++) {
            ClientTest::Config testconfig;
            getSourceConfig(source, testconfig);
            CPPUNIT_ASSERT(testconfig.type);

            SyncSourceConfig* sc = config->getSyncSourceConfig(sources[source].c_str());
            if (!sc) {
                // no configuration yet
                config->setSourceDefaults(sources[source].c_str());
                sc = config->getSyncSourceConfig(sources[source].c_str());
                sc->setURI(testconfig.uri);
                CPPUNIT_ASSERT(sc);
            }

            sc->setType(testconfig.type);
        }
        config->save();
        config->open();

        if (id == "A") {
            /* we are the primary client, create a second one */
            clientB.reset(new TestFileSource("B"));
        }
    }

    virtual int getNumSources() {
        return (int)sources.size();
    }

    virtual void getSourceConfig(int source, Config &config) {
        memset(&config, 0, sizeof(config));

        getTestData(sources[source].c_str(), config);
        config.createSourceA =
            config.createSourceB = createSource;
    }

    virtual ClientTest* getClientB() {
        return clientB.get();
    }

    virtual bool isB64Enabled() {
        return false;
    }

    virtual int sync(
        const int *activeSources,
        SyncMode syncMode,
        const CheckSyncReport &checkReport,
        long maxMsgSize,
        long maxObjSize,
        bool loSupport,
        const char *encoding = 0) {
        SyncSource **syncSources = new SyncSource *[sources.size() + 1];
        int index, numsources = 0;
        memset(syncSources, 0, sizeof(syncSources[0]) * (sources.size() + 1));

        for (index = 0; activeSources[index] >= 0 && index < (int)sources.size(); index++) {
            // rewrite configuration as needed for test
            SyncSourceConfig *sourceConfig = config->getSyncSourceConfig(sources[activeSources[index]].c_str());
            CPPUNIT_ASSERT(sourceConfig);
            sourceConfig->setSync(syncModeKeyword(syncMode));
            sourceConfig->setEncoding(encoding);
            config->getAccessConfig().setMaxMsgSize(maxMsgSize);
            config->getDeviceConfig().setMaxObjSize(maxObjSize);
            config->getDeviceConfig().setLoSupport(loSupport);

            // create sync source using the third change tracking for syncs
            syncSources[numsources++] = createSource(activeSources[index], "S");
        }

        SyncClient client;
        int res = client.sync(*config, syncSources);
        CPPUNIT_ASSERT(client.getSyncReport());

        for (int source = 0; syncSources[source]; source++) {
            delete syncSources[source];
        }
        checkReport.check(res, *client.getSyncReport());
        return res;
    }

private:
    /** either "A" or "B" for first respectively second client */
    std::string clientID;

    /** only in "A": pointer to second client */
    std::auto_ptr<TestFileSource> clientB;

    /** vector of enabled sync sources, identified by a name which SyncClient::getConfig() supports */
    std::vector<std::string> sources;

    /** configuration tree itself */
    std::auto_ptr<DMTClientConfig> config;

    static SyncSource *createSource(ClientTest &client, int source, bool isSourceA) {
        // hand work over to real member function
        return ((TestFileSource &)client).createSource(source, isSourceA ? "A" : "B");
    }

    SyncSource *createSource(int source, const char *trackingSuffix) {
        class RawFileSyncSourceWithReport : public RawFileSyncSource {
        public:
            RawFileSyncSourceWithReport(const char* nodeName, const WCHAR* name, SyncSourceConfig* sc) :
                RawFileSyncSource(name, sc),
                fileNode(nodeName) {
                setReport(&report);
                setFileNode(&fileNode);
                /*
                 * Keeping track if changes is done via time() with a resolution of seconds.
                 * Sleep a bit to ensure that enough time passes.
                 */
#ifdef WIN32
				Sleep(1000);
#else
                sleep(1);
#endif
            }
        private:
            SyncSourceReport report;
            DeviceManagementNode fileNode;
        };

        CPPUNIT_ASSERT(source < (int)sources.size());
        ManagementNode *sourceNode = config->getSyncSourceNode(sources[source].c_str());
        CPPUNIT_ASSERT(sourceNode);
        char *fullName = sourceNode->createFullName();
        std::string nodeName = std::string(fullName) + "/changes_" + trackingSuffix;
        std::string dirName = sources[source] + "_" + clientID;
		WCHAR *name = toWideChar(sources[source].c_str());
        delete [] fullName;
        FileSyncSource *ss = new RawFileSyncSourceWithReport(
            nodeName.c_str(),
            name,
            config->getSyncSourceConfig(sources[source].c_str()));
		delete [] name;
#ifdef WIN32
        _mkdir(dirName.c_str());
#else
		mkdir(dirName.c_str(), S_IRWXU);
#endif
        ss->setDir(dirName.c_str());

        return ss;
    }
};

/**
 * the only purpose of this class is to own the first TestFileSource
 * and to register its tests at program startup
 */
static class RegisterTest {
public:
    RegisterTest() :
        testFileSource("A") {
        testFileSource.registerTests();
    }

private:
    TestFileSource testFileSource;
} registerTest;

/** @} */
/** @endcond */
#endif // ENABLE_INTEGRATION_TESTS
<|endoftext|>


<commit_before>// Copyright (c) 2012-2013 Zeex
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <map>
#include <string>
#include <vector>
#ifdef _WIN32
  #include <windows.h>
#else
  #define _GNU_SOURCE
  #include <dlfcn.h>
#endif
#include <subhook.h>
#include "plugin.h"
#include "version.h"
#include "amxjit/compiler.h"
#if JIT_ASMJIT
  #include "amxjit/compiler-asmjit.h"
#endif
#if JIT_LLVM
  #include "amxjit/compiler-llvm.h"
#endif
#include "amxjit/disasm.h"
#include "amxjit/jit.h"

#if defined __GNUC__ && !defined __MINGW32__
  #define USE_OPCODE_MAP 1
#endif

extern void *pAMXFunctions;

typedef void (*LogprintfType)(const char *format, ...);
static LogprintfType logprintf;

typedef std::map<AMX*, amxjit::JIT*> AmxToJitMap;
static AmxToJitMap amxToJit;

static SubHook ExecHook;
#if USE_OPCODE_MAP
  static cell *opcodeMap = 0;
#endif

static std::string GetModulePath(void *address,
                                 std::size_t maxLength = FILENAME_MAX)
{
  #ifdef _WIN32
    std::vector<char> name(maxLength + 1);
    if (address != 0) {
      MEMORY_BASIC_INFORMATION mbi;
      VirtualQuery(address, &mbi, sizeof(mbi));
      GetModuleFileName((HMODULE)mbi.AllocationBase, &name[0], maxLength);
    }
    return std::string(&name[0]);
  #else
    std::vector<char> name(maxLength + 1);
    if (address != 0) {
      Dl_info info;
      dladdr(address, &info);
      strncpy(&name[0], info.dli_fname, maxLength);
    }
    return std::string(&name[0]);
  #endif
}

static std::string GetFileName(const std::string &path) {
  std::string::size_type pos = path.find_last_of("/\\");
  if (pos != std::string::npos) {
    return path.substr(pos + 1);
  }
  return path;
}

static int AMXAPI amx_Exec_JIT(AMX *amx, cell *retval, int index) {
  #if USE_OPCODE_MAP
    if ((amx->flags & AMX_FLAG_BROWSE) == AMX_FLAG_BROWSE) {
      assert(::opcodeMap != 0);
      *retval = reinterpret_cast<cell>(::opcodeMap);
      return AMX_ERR_NONE;
    }
  #endif
  AmxToJitMap::iterator iterator = ::amxToJit.find(amx);
  if (iterator == ::amxToJit.end()) {
    SubHook::ScopedRemove r(&ExecHook);
    return amx_Exec(amx, retval, index);
  } else {
    amxjit::JIT *jit = iterator->second;
    return jit->Exec(index, retval);
  }
}

class CompileErrorHandler : public amxjit::CompileErrorHandler {
 public:
  virtual void Execute(const amxjit::Instruction &instr) {
    logprintf("[jit] Invalid or unsupported instruction at address %p:",
              instr.GetAddress());
    logprintf("[jit]   => %s", instr.AsString().c_str());
  }
};

PLUGIN_EXPORT unsigned int PLUGIN_CALL Supports() {
  return SUPPORTS_VERSION | SUPPORTS_AMX_NATIVES;
}

PLUGIN_EXPORT bool PLUGIN_CALL Load(void **ppData) {
  logprintf = (LogprintfType)ppData[PLUGIN_DATA_LOGPRINTF];
  pAMXFunctions = reinterpret_cast<void*>(ppData[PLUGIN_DATA_AMX_EXPORTS]);

  void *ptr = SubHook::ReadDst(((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Exec]);
  if (ptr != 0) {
    std::string module = GetFileName(GetModulePath(ptr));
    if (!module.empty()) {
      logprintf("  JIT must be loaded before '%s'", module.c_str());
      return false;
    }
  }

  #if USE_OPCODE_MAP
    // Get opcode table before we hook amx_Exec().
    AMX amx = {0};
    amx.flags |= AMX_FLAG_BROWSE;
    amx_Exec(&amx, reinterpret_cast<cell*>(&::opcodeMap), 0);
    amx.flags &= ~AMX_FLAG_BROWSE;
  #endif

  ExecHook.Install(((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Exec],
                        (void*)amx_Exec_JIT);

  logprintf("  JIT plugin v%s is OK.", PROJECT_VERSION_STRING);
  return true;
}

PLUGIN_EXPORT void PLUGIN_CALL Unload() {
  for (AmxToJitMap::iterator iterator = amxToJit.begin();
       iterator != amxToJit.end(); iterator++) {
    delete iterator->second;
  }
}

PLUGIN_EXPORT int PLUGIN_CALL AmxLoad(AMX *amx) {
  amxjit::JIT *jit = new amxjit::JIT(amx);

  const char *backendString = getenv("JIT_BACKEND");
  if (backendString == 0) {
    backendString = "asmjit";
  }

  amxjit::Compiler *compiler = 0;
  #if JIT_ASMJIT
    if (std::strcmp(backendString, "asmjit") == 0) {
      compiler = new amxjit::CompilerAsmjit;
    }
  #endif
  #if JIT_LLVM
    if (std::strcmp(backendString, "llvm") == 0) {
      compiler = new amxjit::CompilerLLVM;
    }
  #endif

  if (compiler != 0) {
    CompileErrorHandler errorHandler;
    if (!jit->Compile(compiler, &errorHandler)) {
      delete jit;
    } else {
      ::amxToJit.insert(std::make_pair(amx, jit));
    }
  } else {
    logprintf("[jit] Unknown backend '%s'", backendString);
    delete jit;
  }

  delete compiler;
  return AMX_ERR_NONE;
}

PLUGIN_EXPORT int PLUGIN_CALL AmxUnload(AMX *amx) {
  AmxToJitMap::iterator iterator = amxToJit.find(amx);
  if (iterator != amxToJit.end()) {
    delete iterator->second;
    amxToJit.erase(iterator);
  }
  return AMX_ERR_NONE;
}
<commit_msg>Make it possible to prepare code for JIT compilation (issue #35)<commit_after>// Copyright (c) 2012-2013 Zeex
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <map>
#include <string>
#include <vector>
#ifdef _WIN32
  #include <windows.h>
#else
  #define _GNU_SOURCE
  #include <dlfcn.h>
#endif
#include <subhook.h>
#include "plugin.h"
#include "version.h"
#include "amxjit/compiler.h"
#if JIT_ASMJIT
  #include "amxjit/compiler-asmjit.h"
#endif
#if JIT_LLVM
  #include "amxjit/compiler-llvm.h"
#endif
#include "amxjit/disasm.h"
#include "amxjit/jit.h"

#if defined __GNUC__ && !defined __MINGW32__
  #define USE_OPCODE_MAP 1
#endif

extern void *pAMXFunctions;

typedef void (*LogprintfType)(const char *format, ...);
static LogprintfType logprintf;

typedef std::map<AMX*, amxjit::JIT*> AmxToJitMap;
static AmxToJitMap amxToJit;

static SubHook ExecHook;
#if USE_OPCODE_MAP
  static cell *opcodeMap = 0;
#endif

static std::string GetModulePath(void *address,
                                 std::size_t maxLength = FILENAME_MAX)
{
  #ifdef _WIN32
    std::vector<char> name(maxLength + 1);
    if (address != 0) {
      MEMORY_BASIC_INFORMATION mbi;
      VirtualQuery(address, &mbi, sizeof(mbi));
      GetModuleFileName((HMODULE)mbi.AllocationBase, &name[0], maxLength);
    }
    return std::string(&name[0]);
  #else
    std::vector<char> name(maxLength + 1);
    if (address != 0) {
      Dl_info info;
      dladdr(address, &info);
      strncpy(&name[0], info.dli_fname, maxLength);
    }
    return std::string(&name[0]);
  #endif
}

static std::string GetFileName(const std::string &path) {
  std::string::size_type pos = path.find_last_of("/\\");
  if (pos != std::string::npos) {
    return path.substr(pos + 1);
  }
  return path;
}

static int AMXAPI amx_Exec_JIT(AMX *amx, cell *retval, int index) {
  #if USE_OPCODE_MAP
    if ((amx->flags & AMX_FLAG_BROWSE) == AMX_FLAG_BROWSE) {
      assert(::opcodeMap != 0);
      *retval = reinterpret_cast<cell>(::opcodeMap);
      return AMX_ERR_NONE;
    }
  #endif
  AmxToJitMap::iterator iterator = ::amxToJit.find(amx);
  if (iterator == ::amxToJit.end()) {
    SubHook::ScopedRemove r(&ExecHook);
    return amx_Exec(amx, retval, index);
  } else {
    amxjit::JIT *jit = iterator->second;
    return jit->Exec(index, retval);
  }
}

static cell OnJITCompile(AMX *amx) {
  int index;
  if (amx_FindPublic(amx, "OnJITCompile", &index) == AMX_ERR_NONE) {
    cell retval;
    amx_Exec(amx, &retval, index);
    return retval;
  }
  return 0;
}

class CompileErrorHandler : public amxjit::CompileErrorHandler {
 public:
  virtual void Execute(const amxjit::Instruction &instr) {
    logprintf("[jit] Invalid or unsupported instruction at address %p:",
              instr.GetAddress());
    logprintf("[jit]   => %s", instr.AsString().c_str());
  }
};

PLUGIN_EXPORT unsigned int PLUGIN_CALL Supports() {
  return SUPPORTS_VERSION | SUPPORTS_AMX_NATIVES;
}

PLUGIN_EXPORT bool PLUGIN_CALL Load(void **ppData) {
  logprintf = (LogprintfType)ppData[PLUGIN_DATA_LOGPRINTF];
  pAMXFunctions = reinterpret_cast<void*>(ppData[PLUGIN_DATA_AMX_EXPORTS]);

  void *ptr = SubHook::ReadDst(((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Exec]);
  if (ptr != 0) {
    std::string module = GetFileName(GetModulePath(ptr));
    if (!module.empty()) {
      logprintf("  JIT must be loaded before '%s'", module.c_str());
      return false;
    }
  }

  #if USE_OPCODE_MAP
    // Get opcode table before we hook amx_Exec().
    AMX amx = {0};
    amx.flags |= AMX_FLAG_BROWSE;
    amx_Exec(&amx, reinterpret_cast<cell*>(&::opcodeMap), 0);
    amx.flags &= ~AMX_FLAG_BROWSE;
  #endif

  ExecHook.Install(((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Exec],
                        (void*)amx_Exec_JIT);

  logprintf("  JIT plugin v%s is OK.", PROJECT_VERSION_STRING);
  return true;
}

PLUGIN_EXPORT void PLUGIN_CALL Unload() {
  for (AmxToJitMap::iterator iterator = amxToJit.begin();
       iterator != amxToJit.end(); iterator++) {
    delete iterator->second;
  }
}

PLUGIN_EXPORT int PLUGIN_CALL AmxLoad(AMX *amx) {
  if (!OnJITCompile(amx)) {
    logprintf("[jit] Compilation was disabled");
    return AMX_ERR_NONE;
  }

  amxjit::JIT *jit = new amxjit::JIT(amx);

  const char *backendString = getenv("JIT_BACKEND");
  if (backendString == 0) {
    backendString = "asmjit";
  }

  amxjit::Compiler *compiler = 0;
  #if JIT_ASMJIT
    if (std::strcmp(backendString, "asmjit") == 0) {
      compiler = new amxjit::CompilerAsmjit;
    }
  #endif
  #if JIT_LLVM
    if (std::strcmp(backendString, "llvm") == 0) {
      compiler = new amxjit::CompilerLLVM;
    }
  #endif

  if (compiler != 0) {
    CompileErrorHandler errorHandler;
    if (!jit->Compile(compiler, &errorHandler)) {
      delete jit;
    } else {
      ::amxToJit.insert(std::make_pair(amx, jit));
    }
  } else {
    logprintf("[jit] Unknown backend '%s'", backendString);
    delete jit;
  }

  delete compiler;
  return AMX_ERR_NONE;
}

PLUGIN_EXPORT int PLUGIN_CALL AmxUnload(AMX *amx) {
  AmxToJitMap::iterator iterator = amxToJit.find(amx);
  if (iterator != amxToJit.end()) {
    delete iterator->second;
    amxToJit.erase(iterator);
  }
  return AMX_ERR_NONE;
}
<|endoftext|>


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

#include "url.h"

#include "robots.h"

TEST(RobotsTest, NoLeadingUserAgent)
{
    // Assumed to be the default user agent
    std::string content =
        "Disallow: /path\n"
        "Allow: /path/exception\n"
        "Crawl-delay: 5.2\n";
    Rep::Robots robot(content);
    EXPECT_TRUE(robot.allowed("/path/exception", "agent"));
    EXPECT_FALSE(robot.allowed("/path", "agent"));
    EXPECT_NEAR(robot.agent("agent").delay(), 5.2, 0.000001);
}

TEST(RobotsTest, WellFormedCrawlDelay)
{
    std::string content =
        "User-agent: *\n"
        "Crawl-delay: 5.2\n";
    Rep::Robots robot(content);
    EXPECT_NEAR(robot.agent("any").delay(), 5.2, 0.000001);
}

TEST(RobotsTest, MalformedCrawlDelay)
{
    std::string content =
        "User-agent: *\n"
        "Crawl-delay: word\n";
    Rep::Robots robot(content);
    EXPECT_EQ(robot.agent("any").delay(), -1.0);
}

TEST(RobotsTest, HonorsDefaultAgent)
{
    std::string content =
        "User-agent: *\n"
        "Disallow: /tmp\n"
        "\n"
        "User-agent: other-agent\n"
        "Allow: /tmp\n";
    Rep::Robots robot(content);
    EXPECT_FALSE(robot.allowed("/tmp", "agent"));
    EXPECT_TRUE(robot.allowed("/path", "agent"));
}

TEST(RobotsTest, HonorsSpecificAgent)
{
    std::string content =
        "User-agent: *\n"
        "Disallow: /tmp\n"
        "\n"
        "User-agent: agent\n"
        "Allow: /tmp\n";
    Rep::Robots robot(content);
    EXPECT_TRUE(robot.allowed("/tmp", "agent"));
    EXPECT_TRUE(robot.allowed("/path", "agent"));
}

TEST(RobotsTest, Grouping)
{
    std::string content =
        "User-agent: one\n"
        "User-agent: two\n"
        "Disallow: /tmp\n";
    Rep::Robots robot(content);
    EXPECT_FALSE(robot.allowed("/tmp", "one"));
    EXPECT_FALSE(robot.allowed("/tmp", "two"));
}

TEST(RobotsTest, GroupingUnknownKeys)
{
    // When we encounter unknown keys, we should disregard any grouping that may have
    // happened between user agent rules.
    //
    // This is an example from the wild. Despite `Noindex` not being a valid directive,
    // we'll not consider the "*" and "ia_archiver" rules together.
    std::string content =
        "User-agent: *\n"
        "Disallow: /content/2/\n"
        "User-agent: *\n"
        "Noindex: /gb.html\n"
        "Noindex: /content/2/\n"
        "User-agent: ia_archiver\n"
        "Disallow: /\n";
    Rep::Robots robot(content);
    EXPECT_TRUE(robot.allowed("/foo", "agent"));
    EXPECT_FALSE(robot.allowed("/bar", "ia_archiver"));
}

TEST(RobotsTest, SeparatesAgents)
{
    std::string content =
        "User-agent: one\n"
        "Crawl-delay: 1\n"
        "\n"
        "User-agent: two\n"
        "Crawl-delay: 2\n";
    Rep::Robots robot(content);
    EXPECT_NE(robot.agent("one").delay(), robot.agent("two").delay());
}

TEST(RobotsTest, ExposesSitemaps)
{
    std::string content =
        "Sitemap: http://a.com/sitemap.xml\n"
        "Sitemap: http://b.com/sitemap.xml\n";
    Rep::Robots robot(content);
    std::vector<std::string> expected = {
        "http://a.com/sitemap.xml", "http://b.com/sitemap.xml"
    };
    EXPECT_EQ(robot.sitemaps(), expected);
}

TEST(RobotsTest, CaseInsensitivity)
{
    std::string content =
        "User-agent: Agent\n"
        "Disallow: /path\n";
    Rep::Robots robot(content);
    EXPECT_FALSE(robot.allowed("/path", "agent"));
    EXPECT_FALSE(robot.allowed("/path", "aGeNt"));
}

TEST(RobotsTest, Empty)
{
    std::string content;
    Rep::Robots robot(content);
    EXPECT_TRUE(robot.sitemaps().empty());
    EXPECT_TRUE(robot.allowed("/", "agent"));
}

TEST(RobotsTest, Comments)
{
    std::string content =
        "User-Agent: *  # comment saying it's the default agent\n"
        "Disallow: /\n";
    Rep::Robots robot(content);
    EXPECT_FALSE(robot.allowed("/path", "agent"));
}

TEST(RobotsTest, AcceptsFullUrl)
{
    std::string content =
        "User-Agent: agent\n"
        "Disallow: /path;params?query\n";
    Rep::Robots robot(content);
    EXPECT_FALSE(robot.allowed(
        "http://userinfo@exmaple.com:10/path;params?query#fragment", "agent"));
}

TEST(RobotsTest, SkipMalformedLine)
{
    std::string content =
        "User-Agent: agent\n"
        "Disallow /no/colon/in/this/line\n";
    Rep::Robots robot(content);
    EXPECT_TRUE(robot.allowed("/no/colon/in/this/line", "agent"));
}

TEST(RobotsTest, RobotsUrlHttp)
{
    std::string url("http://user@example.com:80/path;params?query#fragment");
    std::string expected("http://example.com/robots.txt");
    EXPECT_EQ(expected, Rep::Robots::robotsUrl(url));
}

TEST(RobotsTest, RobotsUrlHttps)
{
    std::string url("https://user@example.com:443/path;params?query#fragment");
    std::string expected("https://example.com/robots.txt");
    EXPECT_EQ(expected, Rep::Robots::robotsUrl(url));
}

TEST(RobotsTest, RobotsUrlNonDefaultPort)
{
    std::string url("http://user@example.com:8080/path;params?query#fragment");
    std::string expected("http://example.com:8080/robots.txt");
    EXPECT_EQ(expected, Rep::Robots::robotsUrl(url));
}

TEST(RobotsTest, RobotsUrlInvalidPort)
{
    std::string url("http://:::cnn.com/");
    ASSERT_THROW(Rep::Robots::robotsUrl(url), Url::UrlParseException);
}

TEST(RobotsTest, RfcExample)
{
    std::string content =
        "# /robots.txt for http://www.fict.org/\n"
        "# comments to webmaster@fict.org\n"
        "\n"
        "User-agent: unhipbot\n"
        "Disallow: /\n"
        "\n"
        "User-agent: webcrawler\n"
        "User-agent: excite\n"
        "Disallow:\n"
        "\n"
        "User-agent: *\n"
        "Disallow: /org/plans.html\n"
        "Allow: /org/\n"
        "Allow: /serv\n"
        "Allow: /~mak\n"
        "Disallow: /\n";
    Rep::Robots robot(content);

    // The unhip bot
    EXPECT_FALSE(robot.allowed("/", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/index.html", "unhipbot"));
    EXPECT_TRUE(robot.allowed("/robots.txt", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/server.html", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/services/fast.html", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/services/slow.html", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/orgo.gif", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/org/about.html", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/org/plans.html", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/%7Ejim/jim.html", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/%7Emak/mak.html", "unhipbot"));

    // The webcrawler agent
    EXPECT_TRUE(robot.allowed("/", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/index.html", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/robots.txt", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/server.html", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/services/fast.html", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/services/slow.html", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/orgo.gif", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/org/about.html", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/org/plans.html", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/%7Ejim/jim.html", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/%7Emak/mak.html", "webcrawler"));

    // The excite agent
    EXPECT_TRUE(robot.allowed("/", "excite"));
    EXPECT_TRUE(robot.allowed("/index.html", "excite"));
    EXPECT_TRUE(robot.allowed("/robots.txt", "excite"));
    EXPECT_TRUE(robot.allowed("/server.html", "excite"));
    EXPECT_TRUE(robot.allowed("/services/fast.html", "excite"));
    EXPECT_TRUE(robot.allowed("/services/slow.html", "excite"));
    EXPECT_TRUE(robot.allowed("/orgo.gif", "excite"));
    EXPECT_TRUE(robot.allowed("/org/about.html", "excite"));
    EXPECT_TRUE(robot.allowed("/org/plans.html", "excite"));
    EXPECT_TRUE(robot.allowed("/%7Ejim/jim.html", "excite"));
    EXPECT_TRUE(robot.allowed("/%7Emak/mak.html", "excite"));

    // All others
    EXPECT_FALSE(robot.allowed("/", "anything"));
    EXPECT_FALSE(robot.allowed("/index.html", "anything"));
    EXPECT_TRUE(robot.allowed("/robots.txt", "anything"));
    EXPECT_TRUE(robot.allowed("/server.html", "anything"));
    EXPECT_TRUE(robot.allowed("/services/fast.html", "anything"));
    EXPECT_TRUE(robot.allowed("/services/slow.html", "anything"));
    EXPECT_FALSE(robot.allowed("/orgo.gif", "anything"));
    EXPECT_TRUE(robot.allowed("/org/about.html", "anything"));
    EXPECT_FALSE(robot.allowed("/org/plans.html", "anything"));
    EXPECT_FALSE(robot.allowed("/%7Ejim/jim.html", "anything"));
    EXPECT_TRUE(robot.allowed("/%7Emak/mak.html", "anything"));
}

TEST(RobotsTest, IgnoreBOM)
{
    std::string content =
        "\xEF\xBB\xBFuser-agent: *\n"
        "disallow: /disallowed\n";
    Rep::Robots robot(content);
    EXPECT_TRUE(robot.allowed("/", "bot"));
    EXPECT_FALSE(robot.allowed("/disallowed", "bot"));
}

TEST(RobotsTest, Str)
{
    std::string content =
        "User-agent: one\n"
        "Disallow: /foo\n"
        "Allow: /bar\n"
        "User-agent: *\n"
        "Allow: /foo\n"
        "Disallow: /bar\n";

    Rep::Robots robot(content);
    EXPECT_EQ(
        "{\"one\": [Directive(Disallow: /foo), Directive(Allow: /bar)],"
        " \"*\": [Directive(Allow: /foo), Directive(Disallow: /bar)]}",
        robot.str());
}

TEST(RobotsTest, IgnoresExternalDisallow)
{
    std::string content =
        "User-agent: one\n"
        "Allow: /path\n"
        "Disallow: http://b.com/external\n";

    Rep::Robots robot(content, "http://a.com/robots.txt");
    EXPECT_TRUE(robot.allowed("/path", "one"));
    EXPECT_TRUE(robot.allowed("/external", "one"));
}

TEST(RobotsTest, IgnoresExternalAllow)
{
    std::string content =
        "User-agent: one\n"
        "Disallow: /path\n"
        "Allow: http://b.com/path/external\n";

    Rep::Robots robot(content, "http://a.com/robots.txt");
    EXPECT_FALSE(robot.allowed("/path", "one"));
    EXPECT_FALSE(robot.allowed("/path/external", "one"));
}

TEST(RobotsTest, NeverExternalAllowed)
{
    Rep::Robots robot("", "http://a.com/robots.txt");
    EXPECT_FALSE(robot.allowed("http://b.com/", "one"));
}
<commit_msg>add failing test<commit_after>#include <gtest/gtest.h>

#include "url.h"

#include "robots.h"

TEST(RobotsTest, NoLeadingUserAgent)
{
    // Assumed to be the default user agent
    std::string content =
        "Disallow: /path\n"
        "Allow: /path/exception\n"
        "Crawl-delay: 5.2\n";
    Rep::Robots robot(content);
    EXPECT_TRUE(robot.allowed("/path/exception", "agent"));
    EXPECT_FALSE(robot.allowed("/path", "agent"));
    EXPECT_NEAR(robot.agent("agent").delay(), 5.2, 0.000001);
}

TEST(RobotsTest, WellFormedCrawlDelay)
{
    std::string content =
        "User-agent: *\n"
        "Crawl-delay: 5.2\n";
    Rep::Robots robot(content);
    EXPECT_NEAR(robot.agent("any").delay(), 5.2, 0.000001);
}

TEST(RobotsTest, MalformedCrawlDelay)
{
    std::string content =
        "User-agent: *\n"
        "Crawl-delay: word\n";
    Rep::Robots robot(content);
    EXPECT_EQ(robot.agent("any").delay(), -1.0);
}

TEST(RobotsTest, HonorsDefaultAgent)
{
    std::string content =
        "User-agent: *\n"
        "Disallow: /tmp\n"
        "\n"
        "User-agent: other-agent\n"
        "Allow: /tmp\n";
    Rep::Robots robot(content);
    EXPECT_FALSE(robot.allowed("/tmp", "agent"));
    EXPECT_TRUE(robot.allowed("/path", "agent"));
}

TEST(RobotsTest, HonorsSpecificAgent)
{
    std::string content =
        "User-agent: *\n"
        "Disallow: /tmp\n"
        "\n"
        "User-agent: agent\n"
        "Allow: /tmp\n";
    Rep::Robots robot(content);
    EXPECT_TRUE(robot.allowed("/tmp", "agent"));
    EXPECT_TRUE(robot.allowed("/path", "agent"));
}

TEST(RobotsTest, Grouping)
{
    std::string content =
        "User-agent: one\n"
        "User-agent: two\n"
        "Disallow: /tmp\n";
    Rep::Robots robot(content);
    EXPECT_FALSE(robot.allowed("/tmp", "one"));
    EXPECT_FALSE(robot.allowed("/tmp", "two"));
}

TEST(RobotsTest, GroupingUnknownKeys)
{
    // When we encounter unknown keys, we should disregard any grouping that may have
    // happened between user agent rules.
    //
    // This is an example from the wild. Despite `Noindex` not being a valid directive,
    // we'll not consider the "*" and "ia_archiver" rules together.
    std::string content =
        "User-agent: *\n"
        "Disallow: /content/2/\n"
        "User-agent: *\n"
        "Noindex: /gb.html\n"
        "Noindex: /content/2/\n"
        "User-agent: ia_archiver\n"
        "Disallow: /\n";
    Rep::Robots robot(content);
    EXPECT_TRUE(robot.allowed("/foo", "agent"));
    EXPECT_FALSE(robot.allowed("/bar", "ia_archiver"));
}

TEST(RobotsTest, SeparatesAgents)
{
    std::string content =
        "User-agent: one\n"
        "Crawl-delay: 1\n"
        "\n"
        "User-agent: two\n"
        "Crawl-delay: 2\n";
    Rep::Robots robot(content);
    EXPECT_NE(robot.agent("one").delay(), robot.agent("two").delay());
}

TEST(RobotsTest, ExposesSitemaps)
{
    std::string content =
        "Sitemap: http://a.com/sitemap.xml\n"
        "Sitemap: http://b.com/sitemap.xml\n";
    Rep::Robots robot(content);
    std::vector<std::string> expected = {
        "http://a.com/sitemap.xml", "http://b.com/sitemap.xml"
    };
    EXPECT_EQ(robot.sitemaps(), expected);
}

TEST(RobotsTest, CaseInsensitivity)
{
    std::string content =
        "User-agent: Agent\n"
        "Disallow: /path\n";
    Rep::Robots robot(content);
    EXPECT_FALSE(robot.allowed("/path", "agent"));
    EXPECT_FALSE(robot.allowed("/path", "aGeNt"));
}

TEST(RobotsTest, Empty)
{
    std::string content;
    Rep::Robots robot(content);
    EXPECT_TRUE(robot.sitemaps().empty());
    EXPECT_TRUE(robot.allowed("/", "agent"));
}

TEST(RobotsTest, Comments)
{
    std::string content =
        "User-Agent: *  # comment saying it's the default agent\n"
        "Disallow: /\n";
    Rep::Robots robot(content);
    EXPECT_FALSE(robot.allowed("/path", "agent"));
}

TEST(RobotsTest, AcceptsFullUrl)
{
    std::string content =
        "User-Agent: agent\n"
        "Disallow: /path;params?query\n";
    Rep::Robots robot(content);
    EXPECT_FALSE(robot.allowed(
        "http://userinfo@exmaple.com:10/path;params?query#fragment", "agent"));
}

TEST(RobotsTest, SkipMalformedLine)
{
    std::string content =
        "User-Agent: agent\n"
        "Disallow /no/colon/in/this/line\n";
    Rep::Robots robot(content);
    EXPECT_TRUE(robot.allowed("/no/colon/in/this/line", "agent"));
}

TEST(RobotsTest, RobotsUrlHttp)
{
    std::string url("http://user@example.com:80/path;params?query#fragment");
    std::string expected("http://example.com/robots.txt");
    EXPECT_EQ(expected, Rep::Robots::robotsUrl(url));
}

TEST(RobotsTest, RobotsUrlHttps)
{
    std::string url("https://user@example.com:443/path;params?query#fragment");
    std::string expected("https://example.com/robots.txt");
    EXPECT_EQ(expected, Rep::Robots::robotsUrl(url));
}

TEST(RobotsTest, RobotsUrlNonDefaultPort)
{
    std::string url("http://user@example.com:8080/path;params?query#fragment");
    std::string expected("http://example.com:8080/robots.txt");
    EXPECT_EQ(expected, Rep::Robots::robotsUrl(url));
}

TEST(RobotsTest, RobotsUrlInvalidPort)
{
    std::string url("http://:::cnn.com/");
    ASSERT_THROW(Rep::Robots::robotsUrl(url), Url::UrlParseException);
}

TEST(RobotsTest, RfcExample)
{
    std::string content =
        "# /robots.txt for http://www.fict.org/\n"
        "# comments to webmaster@fict.org\n"
        "\n"
        "User-agent: unhipbot\n"
        "Disallow: /\n"
        "\n"
        "User-agent: webcrawler\n"
        "User-agent: excite\n"
        "Disallow:\n"
        "\n"
        "User-agent: *\n"
        "Disallow: /org/plans.html\n"
        "Allow: /org/\n"
        "Allow: /serv\n"
        "Allow: /~mak\n"
        "Disallow: /\n";
    Rep::Robots robot(content);

    // The unhip bot
    EXPECT_FALSE(robot.allowed("/", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/index.html", "unhipbot"));
    EXPECT_TRUE(robot.allowed("/robots.txt", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/server.html", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/services/fast.html", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/services/slow.html", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/orgo.gif", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/org/about.html", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/org/plans.html", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/%7Ejim/jim.html", "unhipbot"));
    EXPECT_FALSE(robot.allowed("/%7Emak/mak.html", "unhipbot"));

    // The webcrawler agent
    EXPECT_TRUE(robot.allowed("/", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/index.html", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/robots.txt", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/server.html", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/services/fast.html", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/services/slow.html", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/orgo.gif", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/org/about.html", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/org/plans.html", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/%7Ejim/jim.html", "webcrawler"));
    EXPECT_TRUE(robot.allowed("/%7Emak/mak.html", "webcrawler"));

    // The excite agent
    EXPECT_TRUE(robot.allowed("/", "excite"));
    EXPECT_TRUE(robot.allowed("/index.html", "excite"));
    EXPECT_TRUE(robot.allowed("/robots.txt", "excite"));
    EXPECT_TRUE(robot.allowed("/server.html", "excite"));
    EXPECT_TRUE(robot.allowed("/services/fast.html", "excite"));
    EXPECT_TRUE(robot.allowed("/services/slow.html", "excite"));
    EXPECT_TRUE(robot.allowed("/orgo.gif", "excite"));
    EXPECT_TRUE(robot.allowed("/org/about.html", "excite"));
    EXPECT_TRUE(robot.allowed("/org/plans.html", "excite"));
    EXPECT_TRUE(robot.allowed("/%7Ejim/jim.html", "excite"));
    EXPECT_TRUE(robot.allowed("/%7Emak/mak.html", "excite"));

    // All others
    EXPECT_FALSE(robot.allowed("/", "anything"));
    EXPECT_FALSE(robot.allowed("/index.html", "anything"));
    EXPECT_TRUE(robot.allowed("/robots.txt", "anything"));
    EXPECT_TRUE(robot.allowed("/server.html", "anything"));
    EXPECT_TRUE(robot.allowed("/services/fast.html", "anything"));
    EXPECT_TRUE(robot.allowed("/services/slow.html", "anything"));
    EXPECT_FALSE(robot.allowed("/orgo.gif", "anything"));
    EXPECT_TRUE(robot.allowed("/org/about.html", "anything"));
    EXPECT_FALSE(robot.allowed("/org/plans.html", "anything"));
    EXPECT_FALSE(robot.allowed("/%7Ejim/jim.html", "anything"));
    EXPECT_TRUE(robot.allowed("/%7Emak/mak.html", "anything"));
}

TEST(RobotsTest, IgnoreBOM)
{
    std::string content =
        "\xEF\xBB\xBFuser-agent: *\n"
        "disallow: /disallowed\n";
    Rep::Robots robot(content);
    EXPECT_TRUE(robot.allowed("/", "bot"));
    EXPECT_FALSE(robot.allowed("/disallowed", "bot"));
}

TEST(RobotsTest, Str)
{
    std::string content =
        "User-agent: one\n"
        "Disallow: /foo\n"
        "Allow: /bar\n"
        "User-agent: *\n"
        "Allow: /foo\n"
        "Disallow: /bar\n";

    Rep::Robots robot(content);
    EXPECT_EQ(
        "{\"one\": [Directive(Disallow: /foo), Directive(Allow: /bar)],"
        " \"*\": [Directive(Allow: /foo), Directive(Disallow: /bar)]}",
        robot.str());
}

TEST(RobotsTest, IgnoresExternalDisallow)
{
    std::string content =
        "User-agent: one\n"
        "Allow: /path\n"
        "Disallow: http://b.com/external\n";

    Rep::Robots robot(content, "http://a.com/robots.txt");
    EXPECT_TRUE(robot.allowed("/path", "one"));
    EXPECT_TRUE(robot.allowed("/external", "one"));
}

TEST(RobotsTest, IgnoresExternalAllow)
{
    std::string content =
        "User-agent: one\n"
        "Disallow: /path\n"
        "Allow: http://b.com/path/external\n";

    Rep::Robots robot(content, "http://a.com/robots.txt");
    EXPECT_FALSE(robot.allowed("/path", "one"));
    EXPECT_FALSE(robot.allowed("/path/external", "one"));
}

TEST(RobotsTest, NeverExternalAllowed)
{
    Rep::Robots robot("", "http://a.com/robots.txt");
    EXPECT_FALSE(robot.allowed("http://b.com/", "one"));
}

TEST(RobotsTest, PrefixStarExample)
{
    std::string content =
        "# /robots.txt for fun and profit\n"
        "\n"
        "User-agent: ohmagad\n"
        "Allow: /\n"
        "Disallow: */dir\n"
        "Disallow: /*/dir\n";
    Rep::Robots robot(content);

    // The ohmagad bot
    EXPECT_TRUE(robot.allowed("/", "ohmagad"));
    EXPECT_TRUE(robot.allowed("/a/b/page.html", "ohmagad"));
    EXPECT_FALSE(robot.allowed("/dir/page.html", "ohmagad"));
    EXPECT_FALSE(robot.allowed("/some/dir/page.html", "ohmagad"));
}
<|endoftext|>