<commit_before>// Copyright (c) 2016-2017 William W. Fisher (at gmail dot com)
// This file is distributed under the MIT License.

#include "ofp/demux/pktsource.h"
#include <sstream>
#include "ofp/bytelist.h"
#include "ofp/log.h"

using namespace ofp;
using namespace ofp::demux;

OFP_BEGIN_IGNORE_PADDING

struct sHandlerInfo {
  PktSource::PktCallback callback;
  void *context;
  UInt32 nanosec_factor;
  UInt32 alignPad;
  UInt32 frameSkip;
  UInt32 packetCount;
  ByteList buffer;
};

OFP_END_IGNORE_PADDING

static void sHandler(u_char *user, const struct pcap_pkthdr *hdr,
                     const u_char *data) {
  sHandlerInfo *info = reinterpret_cast<sHandlerInfo *>(user);
  ByteList &buf = info->buffer;
  Timestamp ts(Unsigned_cast(hdr->ts.tv_sec),
               Unsigned_cast(hdr->ts.tv_usec) * info->nanosec_factor);

  const UInt32 alignPad = info->alignPad;
  const UInt32 frameSkip = info->frameSkip;

  if (frameSkip > hdr->caplen) {
    log_debug("PktSource: caplen less than frameSkip!");
    return;
  }

  const u_char *cap = data + frameSkip;
  const size_t caplen = hdr->caplen - frameSkip;

  buf.replace(buf.data() + alignPad, buf.end(), cap, caplen);
  ByteRange pkt{buf.data() + alignPad, buf.size() - alignPad};
  info->callback(ts, pkt, hdr->len, info->context);
  ++info->packetCount;
}

std::string PktSource::datalink() const {
  if (datalink_ < 0) {
    return "<not open>";
  }

  const char *name = pcap_datalink_val_to_name(datalink_);
  if (name == nullptr) {
    return "<null/error>";
  }

  return name;
}

/// \brief Open capture device to read live packets from the network.
///
/// \param device name of capture interface/device
/// \param filter bpf-filter expression
///
/// \returns true if device is ready to use
bool PktSource::openDevice(const std::string &device,
                           const std::string &filter) {
  if (!isPcapVersionSupported())
    return false;

  if (pcap_)
    close();

  if (!create(device)) {
    close();
    return false;
  }

  if (!activate(device)) {
    close();
    return false;
  }

  if (!checkDatalink()) {
    close();
    return false;
  }

  if (!setFilter(filter)) {
    close();
    return false;
  }

  return true;
}

/// \brief Open capture file to read packets offline.
bool PktSource::openFile(const std::string &file, const std::string &filter) {
  if (!isPcapVersionSupported())
    return false;

  if (pcap_)
    close();

  if (!openOffline(file)) {
    return false;
  }

  // Verify we support this datalink type.
  if (!checkDatalink()) {
    close();
    return false;
  }

  // Set the appropriate bpf filter.
  if (!setFilter(filter)) {
    close();
    return false;
  }

  log_debug("PktSource::openFile", file, "filter", filter);

  return true;
}

/// \brief Close the pcap_t object, if it's open.
///
/// Do nothing if it's already closed.
void PktSource::close() {
  if (pcap_) {
    pcap_close(pcap_);
    pcap_ = nullptr;
    encap_ = ENCAP_UNSUPPORTED;
    datalink_ = -1;
  }
}

bool PktSource::runLoop(PktCallback callback, void *context) {
  assert(pcap_);

  log_debug("PktSource::runLoop entered");

  int kAllPackets = -1;

  sHandlerInfo info;
  info.callback = callback;
  info.context = context;
  info.nanosec_factor = nanosec_factor_;
  info.alignPad = (encapsulation() == ENCAP_ETHERNET) ? 2 : 0;
  info.frameSkip = frameSkip_;
  info.packetCount = packetCount_;
  info.buffer.addZeros(info.alignPad);

  int result = pcap_loop(pcap_, kAllPackets, sHandler, MutableBytePtr(&info));
  if (result != 0) {
    log_debug("pcap_loop returned", result);
  }

  packetCount_ = info.packetCount;

  log_debug("PktSource::runLoop exited");

  return result == 0;
}

bool PktSource::create(const std::string &device) {
  assert(pcap_ == nullptr);

  char errbuf[PCAP_ERRBUF_SIZE];
  pcap_ = pcap_create(device.c_str(), errbuf);
  if (!pcap_) {
    setError("pcap_create", device, errbuf);
    return false;
  }

  int result = pcap_set_snaplen(pcap_, DEFAULT_SNAPLEN);
  if (result) {
    setError("pcap_set_snaplen", device, "failed");
    return false;
  }

  result = pcap_set_promisc(pcap_, DEFAULT_PROMISC);
  if (result) {
    setError("pcap_set_promisc", device, "failed");
    return false;
  }

  result = pcap_set_timeout(pcap_, DEFAULT_TIMEOUT);
  if (result) {
    setError("pcap_set_timeout failed", device, "failed");
    return false;
  }

  result = pcap_set_buffer_size(pcap_, DEFAULT_BUFFER_SIZE);
  if (result) {
    setError("pcap_set_buffer_size", device, "failed");
    return false;
  }

  return true;
}

bool PktSource::openOffline(const std::string &path) {
  char errbuf[PCAP_ERRBUF_SIZE];

#if HAVE_PCAP_OPEN_OFFLINE_WITH_TSTAMP_PRECISION
  // Use the newer `pcap_open_offline_with_tstamp_precision` API if it is
  // available, since it provides access to nanosecond precision timestamps.
  pcap_ = pcap_open_offline_with_tstamp_precision(
      path.c_str(), PCAP_TSTAMP_PRECISION_NANO, errbuf);
  // pcap library will convert to nanoseconds for us.
  nanosec_factor_ = 1;
#else   // !HAVE_PCAP_OPEN_OFFLINE_WITH_TSTAMP_PRECISION
  // Use the original `pcap_open_offline` API if the newer API is not available.
  pcap_ = pcap_open_offline(path.c_str(), errbuf);
  // Convert microseconds to nanoseconds.
  nanosec_factor_ = 1000;
#endif  // !HAVE_PCAP_OPEN_OFFLINE_WITH_TSTAMP_PRECISION

  if (!pcap_) {
    // Set the error string directly; the pcap error message includes the
    // file name.
    error_ = errbuf;
    return false;
  }

  return true;
}

/// \brief Inspect data link type to determine encapsulation.
///
/// \return false if the data link type is not supported
bool PktSource::checkDatalink() {
  assert(pcap_);

  datalink_ = pcap_datalink(pcap_);
  if (datalink_ < 0) {
    setError("pcap_datalink", "", "");
    return false;
  }

  encap_ = lookupEncapsulation(datalink_, &frameSkip_);

  return encap_ != ENCAP_UNSUPPORTED;
}

bool PktSource::setFilter(const std::string &filter) {
  assert(pcap_);

  std::string fullFilter;
  bool supportsVlan = encapsulation() == ENCAP_ETHERNET;

  if (filter.empty()) {
    // If filter is empty, capture everything.
    fullFilter = "";
  } else if (supportsVlan) {
    // If the datalink supports 802.1q vlans, combine our filter with the
    // magic 'vlan' option.
    std::ostringstream oss;
    oss << "(" << filter << ") or (vlan and (" << filter << "))";
    fullFilter = oss.str();
  } else {
    // Use the filter "as is".
    fullFilter = filter;
  }

  struct bpf_program prog;
  int result =
      pcap_compile(pcap_, &prog, fullFilter.c_str(), 1, PCAP_NETMASK_UNKNOWN);
  if (result < 0) {
    setError("pcap_compile", fullFilter, pcap_geterr(pcap_));
    return false;
  }

  result = pcap_setfilter(pcap_, &prog);
  if (result < 0) {
    setError("pcap_setfilter", fullFilter, pcap_geterr(pcap_));
  }

  pcap_freecode(&prog);

  return (result == 0);
}

bool PktSource::activate(const std::string &device) {
  int result = pcap_activate(pcap_);
  if (result == 0) {
    return true;
  }

  const char *errMsg = "Unknown error";

  switch (result) {
    // Warnings log a message and allow activate to continue.
    case PCAP_WARNING_PROMISC_NOTSUP:
      log_warning("pcap_activate:", device,
                  "does not support promiscuous mode --", pcap_geterr(pcap_));
      return true;
#if defined(PCAP_WARNING_TSTAMP_TYPE_NOTSUP)
    case PCAP_WARNING_TSTAMP_TYPE_NOTSUP:
      log_warning(
          "pcap_activate: Capture source does not support timestamp type");
      return true;
#endif  // defined(PCAP_WARNING_TSTAMP_TYPE_NOTSUP)
    case PCAP_WARNING:
      log_warning("pcap_activate: PCAP_WARNING:", pcap_geterr(pcap_));
      return true;

    // Errors set the error message and return false. Log pcap_geterr to the
    // log.
    case PCAP_ERROR_NO_SUCH_DEVICE:
      errMsg = "No such device";
      break;
    case PCAP_ERROR_ACTIVATED:
      errMsg = "Handle already activated";
      break;
    case PCAP_ERROR_PERM_DENIED:
      errMsg = "Permission denied to open capture source";
      break;
#if defined(PCAP_ERROR_PROMISC_PERM_DENIED)
    case PCAP_ERROR_PROMISC_PERM_DENIED:
      errMsg = "Permission denied to put capture source into promiscuous mode";
      break;
#endif  // defined(PCAP_ERROR_PROMISC_PERM_DENIED)
    case PCAP_ERROR_RFMON_NOTSUP:
      errMsg = "Capture source does not support monitor mode";
      break;
    case PCAP_ERROR_IFACE_NOT_UP:
      errMsg = "Capture source is not up";
      break;
    case PCAP_ERROR:
    default:
      errMsg = pcap_geterr(pcap_);
      break;
  }

  setError("pcap_activate", device, errMsg);
  log_error("pcap_activate:", errMsg, "--", pcap_geterr(pcap_));

  return false;
}

void PktSource::setError(const char *func, const std::string &arg,
                         const char *result) {
  std::ostringstream oss;
  oss << func << "(" << arg << "): " << result;
  error_ = oss.str();
}

struct DatalinkInfo {
  int dlType;
  PktSource::Encapsulation encap;
  UInt32 frameSkip;

  bool operator==(int type) const { return dlType == type; }
};

static const DatalinkInfo sDatalinkInfo[] = {
    {DLT_NULL, PktSource::ENCAP_IP, 4},
    {DLT_EN10MB, PktSource::ENCAP_ETHERNET, 0},
    {DLT_RAW, PktSource::ENCAP_IP, 0},
    // TODO(bfish):
    //{ DLT_LINUX_SLL, PktSource::ENCAP_ETHERNET },
    //{ DLT_LOOP  , PktSource::ENCAP_IP }
};

PktSource::Encapsulation PktSource::lookupEncapsulation(int datalink,
                                                        UInt32 *frameSkip) {
  auto iter =
      std::find(std::begin(sDatalinkInfo), std::end(sDatalinkInfo), datalink);
  if (iter != std::end(sDatalinkInfo)) {
    *frameSkip = iter->frameSkip;
    return iter->encap;
  }
  return ENCAP_UNSUPPORTED;
}

/// Return true if we support this version of libpcap (1.1 or newer). Set error_
/// if not. We use version 1.1 for support for Ubuntu Precise.
bool PktSource::isPcapVersionSupported() {
  const char *vers = pcap_lib_version();
  log::fatal_if_null(vers, "pcap_lib_version");

  const unsigned int LIB_MAJOR = 1;
  const unsigned int LIB_MINOR = 1;

  unsigned int major = 0;
  unsigned int minor = 0;
  unsigned int patch = 0;

  if (std::sscanf(vers, "libpcap version %u.%u.%u", &major, &minor, &patch) <
      2) {
    setError("pcap_lib_version", "", vers);
    return false;
  }

  if (major > LIB_MAJOR || (major == LIB_MAJOR && minor >= LIB_MINOR)) {
    return true;
  }

  error_ = "Unsupported: ";
  error_ += vers;

  return false;
}

/// Get list of all devices available for packet capture.
bool PktSource::getDeviceList(std::string *result) {
  result->clear();

  pcap_if_t *devs = nullptr;
  char errbuf[PCAP_ERRBUF_SIZE];

  if (pcap_findalldevs(&devs, errbuf) < 0) {
    assert(devs == nullptr);
    error_ = errbuf;
    return false;
  }

  // devs may still be null if empty.
  if (devs) {
    std::ostringstream oss;
    for (pcap_if_t *dev = devs; dev; dev = dev->next) {
      oss << "  " << dev->name;
      if (dev->description) {
        oss << " (" << dev->description << ')';
      }
      oss << '\n';
    }
    *result = oss.str();

    pcap_freealldevs(devs);
  }

  return true;
}
<commit_msg>Name the optimize argument.<commit_after>// Copyright (c) 2016-2017 William W. Fisher (at gmail dot com)
// This file is distributed under the MIT License.

#include "ofp/demux/pktsource.h"
#include <sstream>
#include "ofp/bytelist.h"
#include "ofp/log.h"

using namespace ofp;
using namespace ofp::demux;

OFP_BEGIN_IGNORE_PADDING

struct sHandlerInfo {
  PktSource::PktCallback callback;
  void *context;
  UInt32 nanosec_factor;
  UInt32 alignPad;
  UInt32 frameSkip;
  UInt32 packetCount;
  ByteList buffer;
};

OFP_END_IGNORE_PADDING

static void sHandler(u_char *user, const struct pcap_pkthdr *hdr,
                     const u_char *data) {
  sHandlerInfo *info = reinterpret_cast<sHandlerInfo *>(user);
  ByteList &buf = info->buffer;
  Timestamp ts(Unsigned_cast(hdr->ts.tv_sec),
               Unsigned_cast(hdr->ts.tv_usec) * info->nanosec_factor);

  const UInt32 alignPad = info->alignPad;
  const UInt32 frameSkip = info->frameSkip;

  if (frameSkip > hdr->caplen) {
    log_debug("PktSource: caplen less than frameSkip!");
    return;
  }

  const u_char *cap = data + frameSkip;
  const size_t caplen = hdr->caplen - frameSkip;

  buf.replace(buf.data() + alignPad, buf.end(), cap, caplen);
  ByteRange pkt{buf.data() + alignPad, buf.size() - alignPad};
  info->callback(ts, pkt, hdr->len, info->context);
  ++info->packetCount;
}

std::string PktSource::datalink() const {
  if (datalink_ < 0) {
    return "<not open>";
  }

  const char *name = pcap_datalink_val_to_name(datalink_);
  if (name == nullptr) {
    return "<null/error>";
  }

  return name;
}

/// \brief Open capture device to read live packets from the network.
///
/// \param device name of capture interface/device
/// \param filter bpf-filter expression
///
/// \returns true if device is ready to use
bool PktSource::openDevice(const std::string &device,
                           const std::string &filter) {
  if (!isPcapVersionSupported())
    return false;

  if (pcap_)
    close();

  if (!create(device)) {
    close();
    return false;
  }

  if (!activate(device)) {
    close();
    return false;
  }

  if (!checkDatalink()) {
    close();
    return false;
  }

  if (!setFilter(filter)) {
    close();
    return false;
  }

  return true;
}

/// \brief Open capture file to read packets offline.
bool PktSource::openFile(const std::string &file, const std::string &filter) {
  if (!isPcapVersionSupported())
    return false;

  if (pcap_)
    close();

  if (!openOffline(file)) {
    return false;
  }

  // Verify we support this datalink type.
  if (!checkDatalink()) {
    close();
    return false;
  }

  // Set the appropriate bpf filter.
  if (!setFilter(filter)) {
    close();
    return false;
  }

  log_debug("PktSource::openFile", file, "filter", filter);

  return true;
}

/// \brief Close the pcap_t object, if it's open.
///
/// Do nothing if it's already closed.
void PktSource::close() {
  if (pcap_) {
    pcap_close(pcap_);
    pcap_ = nullptr;
    encap_ = ENCAP_UNSUPPORTED;
    datalink_ = -1;
  }
}

bool PktSource::runLoop(PktCallback callback, void *context) {
  assert(pcap_);

  log_debug("PktSource::runLoop entered");

  int kAllPackets = -1;

  sHandlerInfo info;
  info.callback = callback;
  info.context = context;
  info.nanosec_factor = nanosec_factor_;
  info.alignPad = (encapsulation() == ENCAP_ETHERNET) ? 2 : 0;
  info.frameSkip = frameSkip_;
  info.packetCount = packetCount_;
  info.buffer.addZeros(info.alignPad);

  int result = pcap_loop(pcap_, kAllPackets, sHandler, MutableBytePtr(&info));
  if (result != 0) {
    log_debug("pcap_loop returned", result);
  }

  packetCount_ = info.packetCount;

  log_debug("PktSource::runLoop exited");

  return result == 0;
}

bool PktSource::create(const std::string &device) {
  assert(pcap_ == nullptr);

  char errbuf[PCAP_ERRBUF_SIZE];
  pcap_ = pcap_create(device.c_str(), errbuf);
  if (!pcap_) {
    setError("pcap_create", device, errbuf);
    return false;
  }

  int result = pcap_set_snaplen(pcap_, DEFAULT_SNAPLEN);
  if (result) {
    setError("pcap_set_snaplen", device, "failed");
    return false;
  }

  result = pcap_set_promisc(pcap_, DEFAULT_PROMISC);
  if (result) {
    setError("pcap_set_promisc", device, "failed");
    return false;
  }

  result = pcap_set_timeout(pcap_, DEFAULT_TIMEOUT);
  if (result) {
    setError("pcap_set_timeout failed", device, "failed");
    return false;
  }

  result = pcap_set_buffer_size(pcap_, DEFAULT_BUFFER_SIZE);
  if (result) {
    setError("pcap_set_buffer_size", device, "failed");
    return false;
  }

  return true;
}

bool PktSource::openOffline(const std::string &path) {
  char errbuf[PCAP_ERRBUF_SIZE];

#if HAVE_PCAP_OPEN_OFFLINE_WITH_TSTAMP_PRECISION
  // Use the newer `pcap_open_offline_with_tstamp_precision` API if it is
  // available, since it provides access to nanosecond precision timestamps.
  pcap_ = pcap_open_offline_with_tstamp_precision(
      path.c_str(), PCAP_TSTAMP_PRECISION_NANO, errbuf);
  // pcap library will convert to nanoseconds for us.
  nanosec_factor_ = 1;
#else   // !HAVE_PCAP_OPEN_OFFLINE_WITH_TSTAMP_PRECISION
  // Use the original `pcap_open_offline` API if the newer API is not available.
  pcap_ = pcap_open_offline(path.c_str(), errbuf);
  // Convert microseconds to nanoseconds.
  nanosec_factor_ = 1000;
#endif  // !HAVE_PCAP_OPEN_OFFLINE_WITH_TSTAMP_PRECISION

  if (!pcap_) {
    // Set the error string directly; the pcap error message includes the
    // file name.
    error_ = errbuf;
    return false;
  }

  return true;
}

/// \brief Inspect data link type to determine encapsulation.
///
/// \return false if the data link type is not supported
bool PktSource::checkDatalink() {
  assert(pcap_);

  datalink_ = pcap_datalink(pcap_);
  if (datalink_ < 0) {
    setError("pcap_datalink", "", "");
    return false;
  }

  encap_ = lookupEncapsulation(datalink_, &frameSkip_);

  return encap_ != ENCAP_UNSUPPORTED;
}

bool PktSource::setFilter(const std::string &filter) {
  assert(pcap_);

  std::string fullFilter;
  bool supportsVlan = encapsulation() == ENCAP_ETHERNET;

  if (filter.empty()) {
    // If filter is empty, capture everything.
    fullFilter = "";
  } else if (supportsVlan) {
    // If the datalink supports 802.1q vlans, combine our filter with the
    // magic 'vlan' option.
    std::ostringstream oss;
    oss << "(" << filter << ") or (vlan and (" << filter << "))";
    fullFilter = oss.str();
  } else {
    // Use the filter "as is".
    fullFilter = filter;
  }

  const int kOptimize = 1;
  struct bpf_program prog;
  int result =
      pcap_compile(pcap_, &prog, fullFilter.c_str(), kOptimize, PCAP_NETMASK_UNKNOWN);
  if (result < 0) {
    setError("pcap_compile", fullFilter, pcap_geterr(pcap_));
    return false;
  }

  result = pcap_setfilter(pcap_, &prog);
  if (result < 0) {
    setError("pcap_setfilter", fullFilter, pcap_geterr(pcap_));
  }

  pcap_freecode(&prog);

  return (result == 0);
}

bool PktSource::activate(const std::string &device) {
  int result = pcap_activate(pcap_);
  if (result == 0) {
    return true;
  }

  const char *errMsg = "Unknown error";

  switch (result) {
    // Warnings log a message and allow activate to continue.
    case PCAP_WARNING_PROMISC_NOTSUP:
      log_warning("pcap_activate:", device,
                  "does not support promiscuous mode --", pcap_geterr(pcap_));
      return true;
#if defined(PCAP_WARNING_TSTAMP_TYPE_NOTSUP)
    case PCAP_WARNING_TSTAMP_TYPE_NOTSUP:
      log_warning(
          "pcap_activate: Capture source does not support timestamp type");
      return true;
#endif  // defined(PCAP_WARNING_TSTAMP_TYPE_NOTSUP)
    case PCAP_WARNING:
      log_warning("pcap_activate: PCAP_WARNING:", pcap_geterr(pcap_));
      return true;

    // Errors set the error message and return false. Log pcap_geterr to the
    // log.
    case PCAP_ERROR_NO_SUCH_DEVICE:
      errMsg = "No such device";
      break;
    case PCAP_ERROR_ACTIVATED:
      errMsg = "Handle already activated";
      break;
    case PCAP_ERROR_PERM_DENIED:
      errMsg = "Permission denied to open capture source";
      break;
#if defined(PCAP_ERROR_PROMISC_PERM_DENIED)
    case PCAP_ERROR_PROMISC_PERM_DENIED:
      errMsg = "Permission denied to put capture source into promiscuous mode";
      break;
#endif  // defined(PCAP_ERROR_PROMISC_PERM_DENIED)
    case PCAP_ERROR_RFMON_NOTSUP:
      errMsg = "Capture source does not support monitor mode";
      break;
    case PCAP_ERROR_IFACE_NOT_UP:
      errMsg = "Capture source is not up";
      break;
    case PCAP_ERROR:
    default:
      errMsg = pcap_geterr(pcap_);
      break;
  }

  setError("pcap_activate", device, errMsg);
  log_error("pcap_activate:", errMsg, "--", pcap_geterr(pcap_));

  return false;
}

void PktSource::setError(const char *func, const std::string &arg,
                         const char *result) {
  std::ostringstream oss;
  oss << func << "(" << arg << "): " << result;
  error_ = oss.str();
}

struct DatalinkInfo {
  int dlType;
  PktSource::Encapsulation encap;
  UInt32 frameSkip;

  bool operator==(int type) const { return dlType == type; }
};

static const DatalinkInfo sDatalinkInfo[] = {
    {DLT_NULL, PktSource::ENCAP_IP, 4},
    {DLT_EN10MB, PktSource::ENCAP_ETHERNET, 0},
    {DLT_RAW, PktSource::ENCAP_IP, 0},
    // TODO(bfish):
    //{ DLT_LINUX_SLL, PktSource::ENCAP_ETHERNET },
    //{ DLT_LOOP  , PktSource::ENCAP_IP }
};

PktSource::Encapsulation PktSource::lookupEncapsulation(int datalink,
                                                        UInt32 *frameSkip) {
  auto iter =
      std::find(std::begin(sDatalinkInfo), std::end(sDatalinkInfo), datalink);
  if (iter != std::end(sDatalinkInfo)) {
    *frameSkip = iter->frameSkip;
    return iter->encap;
  }
  return ENCAP_UNSUPPORTED;
}

/// Return true if we support this version of libpcap (1.1 or newer). Set error_
/// if not. We use version 1.1 for support for Ubuntu Precise.
bool PktSource::isPcapVersionSupported() {
  const char *vers = pcap_lib_version();
  log::fatal_if_null(vers, "pcap_lib_version");

  const unsigned int LIB_MAJOR = 1;
  const unsigned int LIB_MINOR = 1;

  unsigned int major = 0;
  unsigned int minor = 0;
  unsigned int patch = 0;

  if (std::sscanf(vers, "libpcap version %u.%u.%u", &major, &minor, &patch) <
      2) {
    setError("pcap_lib_version", "", vers);
    return false;
  }

  if (major > LIB_MAJOR || (major == LIB_MAJOR && minor >= LIB_MINOR)) {
    return true;
  }

  error_ = "Unsupported: ";
  error_ += vers;

  return false;
}

/// Get list of all devices available for packet capture.
bool PktSource::getDeviceList(std::string *result) {
  result->clear();

  pcap_if_t *devs = nullptr;
  char errbuf[PCAP_ERRBUF_SIZE];

  if (pcap_findalldevs(&devs, errbuf) < 0) {
    assert(devs == nullptr);
    error_ = errbuf;
    return false;
  }

  // devs may still be null if empty.
  if (devs) {
    std::ostringstream oss;
    for (pcap_if_t *dev = devs; dev; dev = dev->next) {
      oss << "  " << dev->name;
      if (dev->description) {
        oss << " (" << dev->description << ')';
      }
      oss << '\n';
    }
    *result = oss.str();

    pcap_freealldevs(devs);
  }

  return true;
}
<|endoftext|>


<commit_before>/*
 * SEGS - Super Entity Game Server
 * http://www.segs.io/
 * Copyright (c) 2006 - 2018 SEGS Team (see AUTHORS.md)
 * This software is licensed under the terms of the 3-clause BSD License. See LICENSE.md for details.
 */

/*!
 * @addtogroup piggtool Projects/CoX/Utilities/piggtool
 * @{
 */

#include <stdio.h>
#include <QtCore/QFileInfo>
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QDebug>
#include <QtCore/QCoreApplication>
#include <QtCore/QCommandLineParser>

#include <vector>
#include <cassert>

namespace {
#pragma pack(push, 1)
struct PiggHeader
{
    int pigg_magic;
    int16_t unused;
    int16_t version;
    int16_t header_size;
    int16_t used_header_bytes;
    uint32_t num_entries;
};

struct PiggInternalHeader
{
    int flag;
    int name_id;
    uint32_t size;
    uint32_t ftime;
    uint32_t offset;
    int unused[6];
    uint32_t packed_size;
};

#pragma pack(pop)
struct Pigg_DataTable
{
    int datapool_flag;
    std::vector<QString> data_parts;
};

struct PiggFile
{
   PiggHeader hdr;
   std::vector<PiggInternalHeader> headers;
   Pigg_DataTable strings_table;
   QString fname;
};

template<class POD>
bool readPOD(QIODevice &src,POD &tgt) {
    return sizeof(POD)==src.read((char *)&tgt,sizeof(POD));
}

//Unpack the ZIP data using qt functions, need to prepend the uncompressed data size to the source data.
QByteArray uncompr_zip(QByteArray &compressed_data,uint32_t size_uncom) {
    compressed_data.prepend( char((size_uncom >> 0) & 0xFF));
    compressed_data.prepend( char((size_uncom >> 8) & 0xFF));
    compressed_data.prepend( char((size_uncom >> 16) & 0xFF));
    compressed_data.prepend( char((size_uncom >> 24) & 0xFF));
    return qUncompress(compressed_data);
}

bool loadDataTable(QFile &src,Pigg_DataTable &target) {
    int table_sizes;
    int num_entries;

    if(!readPOD(src,target.datapool_flag))
        return false;
    if(!readPOD(src,table_sizes))
        return false;
    if(!readPOD(src,num_entries))
        return false;
    while(num_entries>0) {
        uint32_t entrysize;
        if(!readPOD(src,entrysize))
            return false;
        if(entrysize>0)
            target.data_parts.push_back(QString::fromLocal8Bit(src.read(entrysize)));
        num_entries -= entrysize+4;
    }
    return true;
}

bool loadPigg(const QString &fname,PiggFile &pigg)
{
    QFile src_fl(fname);
    if(!src_fl.open(QFile::ReadOnly))
        return false;
    pigg.fname = fname;
    if(false==readPOD(src_fl,pigg.hdr)) {
        qDebug() << "file too short";
        return false;
    }
    if(pigg.hdr.pigg_magic!=0x123) {
        qDebug() << "Bad juju!";
        return false;
    }
    if (pigg.hdr.version > 2)
    {
        qDebug() << "File" <<fname<< "has bad pigg version"<<pigg.hdr.version;
        return  false;
    }
    if (pigg.hdr.header_size != 0x10)
        src_fl.seek(src_fl.pos()+pigg.hdr.header_size - 0x10);
    pigg.headers.resize(pigg.hdr.num_entries);
    assert(pigg.hdr.used_header_bytes==0x30);
    for(PiggInternalHeader &in_hdr : pigg.headers ) {
        readPOD(src_fl,in_hdr);
    }
    int idx=0;
    for(PiggInternalHeader &in_hdr : pigg.headers )
    {
        if (in_hdr.flag != 0x3456)
        {
            qDebug() << "Bad flag on fileheader #"<<idx;
            return false;
        }
        uint32_t internal_fsize = in_hdr.packed_size ? in_hdr.packed_size : in_hdr.size;
        if (in_hdr.offset + internal_fsize - 1 > src_fl.size())
        {
            qDebug() << "File larger than available pigg data - probably corrupt .pigg file!  #"<<idx;
            return false;
        }
        if (in_hdr.name_id < 0)
        {
            qDebug() << "Name id is invalid in #"<<idx;
            return false;
        }
        ++idx;
    }
    loadDataTable(src_fl,pigg.strings_table);
    Pigg_DataTable headers_table; // 'headers' table is not used/needed during file listing/extraction operations
    loadDataTable(src_fl,headers_table);
    if (pigg.strings_table.datapool_flag != 0x6789)
    {
        qDebug() << "Bad string table magic";
        return false;
    }
    if (headers_table.datapool_flag != 0x9ABC) {
        qDebug() << "Bad headers table magic";
        return false;
    }

    qDebug() << "Header loaded";
    return true;
}

void dumpFileList(PiggFile &pigg)
{
    qDebug() << "Filename    -  Compressed Size - Actual size";
    for(PiggInternalHeader & ih : pigg.headers) {
        qDebug().noquote() << pigg.strings_table.data_parts[ih.name_id] << "  " <<ih.packed_size<<"  "<<ih.size;
    }
}

void saveFile(const QString &fname,const QByteArray &data) {
    QFile tgt_fl(fname);
    if(!tgt_fl.open(QFile::WriteOnly)) {
        qWarning() << "failed to open target file" << fname;
        return;
    }
    if(data.size()!=tgt_fl.write(data)) {
        qWarning() << "Failed to write complete file - write error";
    }

}

void extractAllFiles(PiggFile &pigg,const QString &tgt_path)
{
    QFile src_fl(pigg.fname);
    if(!src_fl.open(QFile::ReadOnly)) {
        qWarning() << "failed to open source file" << pigg.fname;
        return;
    }
    const QDir curdir(QDir::current());
    for(const PiggInternalHeader & ih : pigg.headers) {
        const QString target_fname=pigg.strings_table.data_parts[ih.name_id];
        //qDebug().noquote() << "Extracting"<<target_fname;
        src_fl.seek(ih.offset);
        bool was_packed = ih.packed_size!=0;
        QByteArray src_data = src_fl.read(was_packed ? ih.packed_size : ih.size);
        const QFileInfo fi(tgt_path+"/"+target_fname);
        if(!curdir.exists(fi.path()))
            curdir.mkpath(fi.path());
        if(was_packed) {
            QByteArray actual_data = uncompr_zip(src_data,ih.size);
            src_data = actual_data;
        }
        saveFile(tgt_path+"/"+target_fname,src_data);
    }
}
}

int main(int argc, char **argv)
{
    QCoreApplication app(argc,argv);
    QCommandLineParser parser;
    parser.setApplicationDescription("Pigg utility");
    parser.addOptions({
        {"l", "List files in pigg archive"},
        {"x", "Extract all files from pigg archive "},
    });
    parser.addPositionalArgument("pigg_file", "File to process");
    parser.addPositionalArgument("target_directory", "directory to put extracted files in");
    parser.addHelpOption();

    parser.process(app);
    if(parser.positionalArguments().isEmpty() || parser.optionNames().isEmpty()) {
        parser.showHelp(0);
    }
    QStringList positionals = parser.positionalArguments();
    const QString &pigg_name = positionals.constFirst();
    QString target_dir = positionals.count()>1 ? positionals[1] : "data";
    PiggFile hdr;
    if(!loadPigg(pigg_name,hdr))
    {
        qCritical() << "Failed to read"<<pigg_name;
    }
    if(parser.isSet("l")) {
        dumpFileList(hdr);
    }
    else if(parser.isSet("x")) {
        extractAllFiles(hdr,target_dir);
    }
    return 0;
}

//! @}
<commit_msg>Allow specifying a directory to PiggTool for extraction mode (#464)<commit_after>/*
 * SEGS - Super Entity Game Server
 * http://www.segs.io/
 * Copyright (c) 2006 - 2018 SEGS Team (see AUTHORS.md)
 * This software is licensed under the terms of the 3-clause BSD License. See LICENSE.md for details.
 */

/*!
 * @addtogroup piggtool Projects/CoX/Utilities/piggtool
 * @{
 */

#include <stdio.h>
#include <QtCore/QFileInfo>
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QDebug>
#include <QtCore/QCoreApplication>
#include <QtCore/QCommandLineParser>

#include <vector>
#include <cassert>

namespace {
#pragma pack(push, 1)
struct PiggHeader
{
    int pigg_magic;
    int16_t unused;
    int16_t version;
    int16_t header_size;
    int16_t used_header_bytes;
    uint32_t num_entries;
};

struct PiggInternalHeader
{
    int flag;
    int name_id;
    uint32_t size;
    uint32_t ftime;
    uint32_t offset;
    int unused[6];
    uint32_t packed_size;
};

#pragma pack(pop)
struct Pigg_DataTable
{
    int datapool_flag;
    std::vector<QString> data_parts;
};

struct PiggFile
{
   PiggHeader hdr;
   std::vector<PiggInternalHeader> headers;
   Pigg_DataTable strings_table;
   QString fname;
};

template<class POD>
bool readPOD(QIODevice &src,POD &tgt) {
    return sizeof(POD)==src.read((char *)&tgt,sizeof(POD));
}

//Unpack the ZIP data using qt functions, need to prepend the uncompressed data size to the source data.
QByteArray uncompr_zip(QByteArray &compressed_data,uint32_t size_uncom) {
    compressed_data.prepend( char((size_uncom >> 0) & 0xFF));
    compressed_data.prepend( char((size_uncom >> 8) & 0xFF));
    compressed_data.prepend( char((size_uncom >> 16) & 0xFF));
    compressed_data.prepend( char((size_uncom >> 24) & 0xFF));
    return qUncompress(compressed_data);
}

bool loadDataTable(QFile &src,Pigg_DataTable &target) {
    int table_sizes;
    int num_entries;

    if(!readPOD(src,target.datapool_flag))
        return false;
    if(!readPOD(src,table_sizes))
        return false;
    if(!readPOD(src,num_entries))
        return false;
    while(num_entries>0) {
        uint32_t entrysize;
        if(!readPOD(src,entrysize))
            return false;
        if(entrysize>0)
            target.data_parts.push_back(QString::fromLocal8Bit(src.read(entrysize)));
        num_entries -= entrysize+4;
    }
    return true;
}

bool loadPigg(const QString &fname,PiggFile &pigg)
{
    QFile src_fl(fname);
    if(!src_fl.open(QFile::ReadOnly))
        return false;
    pigg.fname = fname;
    if(false==readPOD(src_fl,pigg.hdr)) {
        qDebug() << "file too short";
        return false;
    }
    if(pigg.hdr.pigg_magic!=0x123) {
        qDebug() << "Bad juju!";
        return false;
    }
    if (pigg.hdr.version > 2)
    {
        qDebug() << "File" <<fname<< "has bad pigg version"<<pigg.hdr.version;
        return  false;
    }
    if (pigg.hdr.header_size != 0x10)
        src_fl.seek(src_fl.pos()+pigg.hdr.header_size - 0x10);
    pigg.headers.resize(pigg.hdr.num_entries);
    assert(pigg.hdr.used_header_bytes==0x30);
    for(PiggInternalHeader &in_hdr : pigg.headers ) {
        readPOD(src_fl,in_hdr);
    }
    int idx=0;
    for(PiggInternalHeader &in_hdr : pigg.headers )
    {
        if (in_hdr.flag != 0x3456)
        {
            qDebug() << "Bad flag on fileheader #"<<idx;
            return false;
        }
        uint32_t internal_fsize = in_hdr.packed_size ? in_hdr.packed_size : in_hdr.size;
        if (in_hdr.offset + internal_fsize - 1 > src_fl.size())
        {
            qDebug() << "File larger than available pigg data - probably corrupt .pigg file!  #"<<idx;
            return false;
        }
        if (in_hdr.name_id < 0)
        {
            qDebug() << "Name id is invalid in #"<<idx;
            return false;
        }
        ++idx;
    }
    loadDataTable(src_fl,pigg.strings_table);
    Pigg_DataTable headers_table; // 'headers' table is not used/needed during file listing/extraction operations
    loadDataTable(src_fl,headers_table);
    if (pigg.strings_table.datapool_flag != 0x6789)
    {
        qDebug() << "Bad string table magic";
        return false;
    }
    if (headers_table.datapool_flag != 0x9ABC) {
        qDebug() << "Bad headers table magic";
        return false;
    }

    return true;
}

void dumpFileList(PiggFile &pigg)
{
    qDebug() << "Filename    -  Compressed Size - Actual size";
    for(PiggInternalHeader & ih : pigg.headers) {
        qDebug().noquote() << pigg.strings_table.data_parts[ih.name_id] << "  " <<ih.packed_size<<"  "<<ih.size;
    }
}

void saveFile(const QString &fname,const QByteArray &data) {
    QFile tgt_fl(fname);
    if(!tgt_fl.open(QFile::WriteOnly)) {
        qWarning() << "failed to open target file" << fname;
        return;
    }
    if(data.size()!=tgt_fl.write(data)) {
        qWarning() << "Failed to write complete file - write error";
    }

}

bool extractAllFiles(PiggFile &pigg, const QString &tgt_path)
{
    QFile src_fl(pigg.fname);

    if (!src_fl.open(QFile::ReadOnly)) {
        qWarning() << "failed to open source file" << pigg.fname;
        return false;
    }

    qInfo() << "Found" << pigg.headers.size() << "internal files in:" << pigg.fname;

    const QDir curdir(QDir::current());

    for(const PiggInternalHeader & ih : pigg.headers) {
        const QString target_fname=pigg.strings_table.data_parts[ih.name_id];
        //qDebug().noquote() << "Extracting"<<target_fname;

        src_fl.seek(ih.offset);

        bool was_packed = ih.packed_size!=0;
        QByteArray src_data = src_fl.read(was_packed ? ih.packed_size : ih.size);

        const QFileInfo fi(tgt_path+"/"+target_fname);

        if(!curdir.exists(fi.path()))
            curdir.mkpath(fi.path());

        if(was_packed) {
            QByteArray actual_data = uncompr_zip(src_data,ih.size);
            src_data = actual_data;
        }

        saveFile(tgt_path+"/"+target_fname,src_data);
    }

    return true;
}

bool extractFile(const QString &pigg_filename, const QString &target_directory)
{
    qInfo() << "Reading Pigg file:" << pigg_filename;

    PiggFile header;

    if (!loadPigg(pigg_filename, header))
    {
        qCritical() << "Failed to read Pigg file:" << pigg_filename;
        return false;
    }

    return extractAllFiles(header, target_directory);
}

void extractFilesFromPath(const QString &source_path, const QString &target_directory)
{
    const QFileInfo file_info(source_path);

    if (file_info.isDir())
    {
        const auto files = QDir(source_path).entryList(QStringList() << "*.pigg", QDir::Files);
        qInfo() << "Found" << files.size() << ".pigg files.";

        for (const auto file : files)
            extractFile(source_path + "/" + file, target_directory);
    }
    else if (file_info.isFile())
    {
        extractFile(source_path, target_directory);
    }
}

}

int main(int argc, char **argv)
{
    QCoreApplication app(argc,argv);
    QCommandLineParser parser;
    parser.setApplicationDescription("Pigg utility");
    parser.addOptions({
        {"l", "List files in pigg archive"},
        {"x", "Extract all files from pigg archive "},
    });
    parser.addPositionalArgument("pigg_file", "File to process");
    parser.addPositionalArgument("target_directory", "directory to put extracted files in");
    parser.addHelpOption();

    parser.process(app);

    if (parser.positionalArguments().isEmpty() || parser.optionNames().isEmpty())
        parser.showHelp(0);

    QStringList positionals = parser.positionalArguments();
    const QString &pigg_name = positionals.constFirst();
    QString target_dir = positionals.count()>1 ? positionals[1] : "data";

    if (parser.isSet("l"))
    {
        PiggFile header;

        if (loadPigg(pigg_name, header))
          dumpFileList(header);
    }
    else if (parser.isSet("x"))
    {
        extractFilesFromPath(pigg_name, target_dir);
    }

    return 0;
}

//! @}
<|endoftext|>


<commit_before>/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * This file is part of the libetonyek project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <libetonyek/libetonyek.h>

#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/logic/tribool.hpp>

#include <libxml/xmlreader.h>

#include "libetonyek_utils.h"
#include "libetonyek_xml.h"
#include "IWORKPresentationRedirector.h"
#include "IWORKSpreadsheetRedirector.h"
#include "IWORKTextRedirector.h"
#include "IWORKTokenizer.h"
#include "IWORKZlibStream.h"
#include "KEY1Parser.h"
#include "KEY2Parser.h"
#include "KEY2Token.h"
#include "KEYCollector.h"
#include "KEYDictionary.h"
#include "NUMCollector.h"
#include "NUMDictionary.h"
#include "NUM1Parser.h"
#include "NUM1Token.h"
#include "PAGCollector.h"
#include "PAG1Parser.h"
#include "PAG1Token.h"
#include "PAGDictionary.h"

using boost::logic::indeterminate;
using boost::logic::tribool;
using boost::scoped_ptr;
using boost::shared_ptr;
using std::string;

using librevenge::RVNG_SEEK_SET;

namespace libetonyek
{

namespace
{

enum CheckType
{
  CHECK_TYPE_NONE = 0,
  CHECK_TYPE_KEYNOTE = 1 << 1,
  CHECK_TYPE_NUMBERS = 1 << 2,
  CHECK_TYPE_PAGES = 1 << 3,
  CHECK_TYPE_ANY = CHECK_TYPE_KEYNOTE | CHECK_TYPE_NUMBERS | CHECK_TYPE_PAGES
};

struct DetectionInfo
{
  DetectionInfo();

  RVNGInputStreamPtr_t input;
  RVNGInputStreamPtr_t package;
  EtonyekDocument::Confidence confidence;
  EtonyekDocument::Type type;
  unsigned version;
};

DetectionInfo::DetectionInfo()
  : input()
  , package()
  , confidence(EtonyekDocument::CONFIDENCE_NONE)
  , type(EtonyekDocument::TYPE_UNKNOWN)
  , version(0)
{
}

typedef bool (*ProbeXMLFun_t)(const RVNGInputStreamPtr_t &, unsigned &, xmlTextReaderPtr);

std::string queryAttribute(xmlTextReaderPtr reader, const int name, const int ns, const IWORKTokenizer &tokenizer)
{
  if (xmlTextReaderHasAttributes(reader))
  {
    int ret = xmlTextReaderMoveToFirstAttribute(reader);
    while (1 == ret)
    {
      const int id = tokenizer.getQualifiedId(char_cast(xmlTextReaderConstLocalName(reader)), char_cast(xmlTextReaderConstNamespaceUri(reader)));
      if ((ns | name) == id)
        return char_cast(xmlTextReaderConstValue(reader));

      ret = xmlTextReaderMoveToNextAttribute(reader);
    }
  }

  return "";
}

bool probeKeynote1XML(const RVNGInputStreamPtr_t &input, unsigned &version, xmlTextReaderPtr reader)
{
  // TODO: implement me
  (void) input;
  (void) version;
  (void) reader;
  return false;
}

bool probeKeynote2XML(const RVNGInputStreamPtr_t &input, unsigned &version, xmlTextReaderPtr reader)
{
  if (input->isEnd())
    return false;

  const IWORKTokenizer &tokenizer(KEY2Token::getTokenizer());
  assert(reader);

  const int id = tokenizer.getQualifiedId(char_cast(xmlTextReaderConstLocalName(reader)), char_cast(xmlTextReaderConstNamespaceUri(reader)));

  if ((KEY2Token::NS_URI_KEY | KEY2Token::presentation) == id)
  {
    const std::string v = queryAttribute(reader, KEY2Token::version, KEY2Token::NS_URI_KEY, tokenizer);

    switch (tokenizer.getId(v.c_str()))
    {
    case KEY2Token::VERSION_STR_2 :
      version = 2;
      return true;
    case KEY2Token::VERSION_STR_3 :
      version = 3;
      return true;
    case KEY2Token::VERSION_STR_4 :
      version = 4;
      return true;
    case KEY2Token::VERSION_STR_5 :
      version = 5;
      return true;
    }
  }

  return false;
}

bool probeKeynoteXML(const RVNGInputStreamPtr_t &input, unsigned &version, xmlTextReaderPtr reader)
{
  if (probeKeynote2XML(input, version, reader))
    return true;

  input->seek(0, RVNG_SEEK_SET);

  return probeKeynote1XML(input, version, reader);
}

bool probeNumbersXML(const RVNGInputStreamPtr_t &input, unsigned &version, xmlTextReaderPtr reader)
{
  if (input->isEnd())
    return false;

  const IWORKTokenizer &tokenizer(NUM1Token::getTokenizer());
  assert(reader);

  const int id = tokenizer.getQualifiedId(char_cast(xmlTextReaderConstLocalName(reader)), char_cast(xmlTextReaderConstNamespaceUri(reader)));

  if ((NUM1Token::NS_URI_LS | NUM1Token::document) == id)
  {
    const std::string v = queryAttribute(reader, NUM1Token::version, NUM1Token::NS_URI_LS, tokenizer);

    switch (tokenizer.getId(v.c_str()))
    {
    case NUM1Token::VERSION_STR_2 :
      version = 2;
      return true;
    }
  }

  return false;
}

bool probePagesXML(const RVNGInputStreamPtr_t &input, unsigned &version, xmlTextReaderPtr reader)
{
  if (input->isEnd())
    return false;

  const IWORKTokenizer &tokenizer(PAG1Token::getTokenizer());
  assert(reader);

  const int id = tokenizer.getQualifiedId(char_cast(xmlTextReaderConstLocalName(reader)), char_cast(xmlTextReaderConstNamespaceUri(reader)));

  if ((PAG1Token::NS_URI_SL | PAG1Token::document) == id)
  {
    const std::string v = queryAttribute(reader, PAG1Token::version, PAG1Token::NS_URI_SL, tokenizer);

    switch (tokenizer.getId(v.c_str()))
    {
    case PAG1Token::VERSION_STR_4 :
      version = 4;
      return true;
    }
  }

  return false;
}

bool probeXMLImpl(const RVNGInputStreamPtr_t &input, const ProbeXMLFun_t probe, const EtonyekDocument::Type type, DetectionInfo &info)
{
  const shared_ptr<xmlTextReader> reader(xmlReaderForIO(readFromStream, closeStream, input.get(), "", 0, 0), xmlFreeTextReader);
  if (!reader)
    return false;

  int ret = 0;
  do
  {
    ret = xmlTextReaderRead(reader.get());
  }
  while ((1 == ret) && (XML_READER_TYPE_ELEMENT != xmlTextReaderNodeType(reader.get())));

  if (1 != ret)
    return false;

  if (probe(input, info.version, reader.get()))
  {
    info.type = type;
    return true;
  }

  return false;
}

bool probeXML(const ProbeXMLFun_t probe, const EtonyekDocument::Type type, tribool &isGzipped, DetectionInfo &info)
{
  if (isGzipped || indeterminate(isGzipped))
  {
    try
    {
      const RVNGInputStreamPtr_t uncompressed(new IWORKZlibStream(info.input));
      isGzipped = true;

      if (probeXMLImpl(uncompressed, probe, type, info))
      {
        info.input = uncompressed;
        return true;
      }
      else
      {
        return false; // compressed, but invalid format
      }
    }
    catch (...)
    {
      if (isGzipped) // decompression failed? most probably a broken file...
        return false;

      isGzipped = false;
    }

    info.input->seek(0, RVNG_SEEK_SET);
  }

  assert(!isGzipped);

  return probeXMLImpl(info.input, probe, type, info);
}

bool detect(const RVNGInputStreamPtr_t &input, unsigned checkTypes, DetectionInfo &info)
{
  info.confidence = EtonyekDocument::CONFIDENCE_SUPPORTED_PART;
  bool isXML = true;
  tribool isGzipped = indeterminate;
  tribool isKeynote1 = indeterminate;

  if (input->isStructured())
  {
    info.package = input;

    // check which format it might be
    if (CHECK_TYPE_KEYNOTE & checkTypes)
    {
      if (input->existsSubStream("index.apxl"))
      {
        checkTypes = CHECK_TYPE_KEYNOTE;
        isGzipped = false;
        isKeynote1 = false;
        info.input.reset(input->getSubStreamByName("index.apxl"));
      }
      else if (input->existsSubStream("index.apxl.gz"))
      {
        checkTypes = CHECK_TYPE_KEYNOTE;
        isGzipped = true;
        isKeynote1 = false;
        info.input.reset(input->getSubStreamByName("index.apxl.gz"));
      }
      else if (input->existsSubStream("presentation.apxl"))
      {
        checkTypes = CHECK_TYPE_KEYNOTE;
        isGzipped = false;
        isKeynote1 = true;
        info.input.reset(input->getSubStreamByName("presentation.apxl"));
      }
      else if (input->existsSubStream("presentation.apxl.gz"))
      {
        checkTypes = CHECK_TYPE_KEYNOTE;
        isGzipped = true;
        isKeynote1 = true;
        info.input.reset(input->getSubStreamByName("presentation.apxl.gz"));
      }
    }

    if ((CHECK_TYPE_NUMBERS | CHECK_TYPE_PAGES) & checkTypes)
    {
      if (input->existsSubStream("index.xml"))
      {
        checkTypes &= (CHECK_TYPE_NUMBERS | CHECK_TYPE_PAGES);
        isGzipped = false;
        info.input.reset(input->getSubStreamByName("index.xml"));
      }
      else if (input->existsSubStream("index.xml.gz"))
      {
        checkTypes &= (CHECK_TYPE_NUMBERS | CHECK_TYPE_PAGES);
        isGzipped = false;
        info.input.reset(input->getSubStreamByName("index.xml.gz"));
      }
    }

    if (!info.input && (CHECK_TYPE_ANY & checkTypes))
    {
      if (input->existsSubStream("Index.zip"))
      {
        isXML = false;
        info.input.reset(input->getSubStreamByName("Index.zip"));
      }
    }

    if (!info.input)
    {
      // nothing detected
      // TODO: this might also be Index.zip...
      return EtonyekDocument::CONFIDENCE_NONE;
    }

    info.confidence = EtonyekDocument::CONFIDENCE_EXCELLENT; // this is either a valid package of a false positive
  }
  else
  {
    info.input = input;
  }

  assert(bool(info.input));

  if (isXML)
  {
    assert(CHECK_TYPE_ANY & checkTypes);
    assert(!info.input->isStructured());

    info.input->seek(0, RVNG_SEEK_SET);

    if (CHECK_TYPE_KEYNOTE & checkTypes)
    {
      const ProbeXMLFun_t probe = (isKeynote1 ? probeKeynote1XML : ((!isKeynote1) ? probeKeynote2XML : probeKeynoteXML));
      if (probeXML(probe, EtonyekDocument::TYPE_KEYNOTE, isGzipped, info))
        return true;

      info.input->seek(0, RVNG_SEEK_SET);
    }

    if (CHECK_TYPE_NUMBERS & checkTypes)
    {
      if (probeXML(probeNumbersXML, EtonyekDocument::TYPE_NUMBERS, isGzipped, info))
        return true;

      info.input->seek(0, RVNG_SEEK_SET);
    }

    if (CHECK_TYPE_PAGES & checkTypes)
    {
      if (probeXML(probePagesXML, EtonyekDocument::TYPE_PAGES, isGzipped, info))
        return true;
    }
  }
  else
  {
    // TODO: detect type in binary format
  }

  return EtonyekDocument::CONFIDENCE_NONE;
}

}

namespace
{

shared_ptr<IWORKParser> makeKeynoteParser(const unsigned version, const RVNGInputStreamPtr_t &input, const RVNGInputStreamPtr_t &package, KEYCollector *const collector, KEYDictionary &dict)
{
  shared_ptr<IWORKParser> parser;

  if (1 == version)
    parser.reset(new KEY1Parser(input, package, collector));
  else if ((2 <= version) && (5 >= version))
    parser.reset(new KEY2Parser(input, package, collector, dict));
  else
    assert(0);

  return parser;
}

}

ETONYEKAPI EtonyekDocument::Confidence EtonyekDocument::isSupported(librevenge::RVNGInputStream *const input, EtonyekDocument::Type *type) try
{
  if (!input)
    return CONFIDENCE_NONE;

  if (type)
    *type = TYPE_UNKNOWN;

  DetectionInfo info;

  if (detect(RVNGInputStreamPtr_t(input, EtonyekDummyDeleter()), CHECK_TYPE_ANY, info))
  {
    assert(TYPE_UNKNOWN != info.type);
    assert(CONFIDENCE_NONE != info.confidence);
    assert(bool(info.input));

    if (type)
      *type = info.type;
    return info.confidence;
  }

  return CONFIDENCE_NONE;
}
catch (...)
{
  return CONFIDENCE_NONE;
}

ETONYEKAPI bool EtonyekDocument::parse(librevenge::RVNGInputStream *const input, librevenge::RVNGPresentationInterface *const generator) try
{
  if (!input || !generator)
    return false;

  DetectionInfo info;

  if (!detect(RVNGInputStreamPtr_t(input, EtonyekDummyDeleter()), CHECK_TYPE_KEYNOTE, info))
    return false;

  assert(TYPE_UNKNOWN != info.type);
  assert(CONFIDENCE_NONE != info.confidence);
  assert(bool(info.input));
  assert(0 != info.version);

  info.input->seek(0, librevenge::RVNG_SEEK_SET);

  KEYDictionary dict;
  IWORKPresentationRedirector redirector(generator);
  KEYCollector collector(&redirector);
  const shared_ptr<IWORKParser> parser = makeKeynoteParser(info.version, info.input, info.package, &collector, dict);
  return parser->parse();
}
catch (...)
{
  return false;
}

ETONYEKAPI bool EtonyekDocument::parse(librevenge::RVNGInputStream *const input, librevenge::RVNGSpreadsheetInterface *const document) try
{
  if (!input || !document)
    return false;

  DetectionInfo info;

  if (!detect(RVNGInputStreamPtr_t(input, EtonyekDummyDeleter()), CHECK_TYPE_NUMBERS, info))
    return false;

  assert(TYPE_UNKNOWN != info.type);
  assert(CONFIDENCE_NONE != info.confidence);
  assert(bool(info.input));
  assert(0 != info.version);

  info.input->seek(0, librevenge::RVNG_SEEK_SET);

  IWORKSpreadsheetRedirector redirector(document);
  NUMCollector collector(&redirector);
  NUMDictionary dict;
  NUM1Parser parser(info.input, info.package, &collector, &dict);
  return parser.parse();
}
catch (...)
{
  return false;
}

ETONYEKAPI bool EtonyekDocument::parse(librevenge::RVNGInputStream *const input, librevenge::RVNGTextInterface *const document) try
{
  if (!input || !document)
    return false;

  DetectionInfo info;

  if (!detect(RVNGInputStreamPtr_t(input, EtonyekDummyDeleter()), CHECK_TYPE_PAGES, info))
    return false;

  assert(TYPE_UNKNOWN != info.type);
  assert(CONFIDENCE_NONE != info.confidence);
  assert(bool(info.input));
  assert(0 != info.version);

  info.input->seek(0, librevenge::RVNG_SEEK_SET);

  IWORKTextRedirector redirector(document);
  PAGCollector collector(&redirector);
  PAGDictionary dict;
  PAG1Parser parser(info.input, info.package, &collector, &dict);
  return parser.parse();
}
catch (...)
{
  return false;
}

}

/* vim:set shiftwidth=2 softtabstop=2 expandtab: */
<commit_msg>blind fix for windows build error<commit_after>/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * This file is part of the libetonyek project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <libetonyek/libetonyek.h>

#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/logic/tribool.hpp>

#include <libxml/xmlreader.h>

#include "libetonyek_utils.h"
#include "libetonyek_xml.h"
#include "IWORKPresentationRedirector.h"
#include "IWORKSpreadsheetRedirector.h"
#include "IWORKTextRedirector.h"
#include "IWORKTokenizer.h"
#include "IWORKZlibStream.h"
#include "KEY1Parser.h"
#include "KEY2Parser.h"
#include "KEY2Token.h"
#include "KEYCollector.h"
#include "KEYDictionary.h"
#include "NUMCollector.h"
#include "NUMDictionary.h"
#include "NUM1Parser.h"
#include "NUM1Token.h"
#include "PAGCollector.h"
#include "PAG1Parser.h"
#include "PAG1Token.h"
#include "PAGDictionary.h"

using boost::logic::indeterminate;
using boost::logic::tribool;
using boost::scoped_ptr;
using boost::shared_ptr;
using std::string;

using librevenge::RVNG_SEEK_SET;

namespace libetonyek
{

namespace
{

enum CheckType
{
  CHECK_TYPE_NONE = 0,
  CHECK_TYPE_KEYNOTE = 1 << 1,
  CHECK_TYPE_NUMBERS = 1 << 2,
  CHECK_TYPE_PAGES = 1 << 3,
  CHECK_TYPE_ANY = CHECK_TYPE_KEYNOTE | CHECK_TYPE_NUMBERS | CHECK_TYPE_PAGES
};

struct DetectionInfo
{
  DetectionInfo();

  RVNGInputStreamPtr_t input;
  RVNGInputStreamPtr_t package;
  EtonyekDocument::Confidence confidence;
  EtonyekDocument::Type type;
  unsigned version;
};

DetectionInfo::DetectionInfo()
  : input()
  , package()
  , confidence(EtonyekDocument::CONFIDENCE_NONE)
  , type(EtonyekDocument::TYPE_UNKNOWN)
  , version(0)
{
}

typedef bool (*ProbeXMLFun_t)(const RVNGInputStreamPtr_t &, unsigned &, xmlTextReaderPtr);

std::string queryAttribute(xmlTextReaderPtr reader, const int name, const int ns, const IWORKTokenizer &tokenizer)
{
  if (xmlTextReaderHasAttributes(reader))
  {
    int ret = xmlTextReaderMoveToFirstAttribute(reader);
    while (1 == ret)
    {
      const int id = tokenizer.getQualifiedId(char_cast(xmlTextReaderConstLocalName(reader)), char_cast(xmlTextReaderConstNamespaceUri(reader)));
      if ((ns | name) == id)
        return char_cast(xmlTextReaderConstValue(reader));

      ret = xmlTextReaderMoveToNextAttribute(reader);
    }
  }

  return "";
}

bool probeKeynote1XML(const RVNGInputStreamPtr_t &input, unsigned &version, xmlTextReaderPtr reader)
{
  // TODO: implement me
  (void) input;
  (void) version;
  (void) reader;
  return false;
}

bool probeKeynote2XML(const RVNGInputStreamPtr_t &input, unsigned &version, xmlTextReaderPtr reader)
{
  if (input->isEnd())
    return false;

  const IWORKTokenizer &tokenizer(KEY2Token::getTokenizer());
  assert(reader);

  const int id = tokenizer.getQualifiedId(char_cast(xmlTextReaderConstLocalName(reader)), char_cast(xmlTextReaderConstNamespaceUri(reader)));

  if ((KEY2Token::NS_URI_KEY | KEY2Token::presentation) == id)
  {
    const std::string v = queryAttribute(reader, KEY2Token::version, KEY2Token::NS_URI_KEY, tokenizer);

    switch (tokenizer.getId(v.c_str()))
    {
    case KEY2Token::VERSION_STR_2 :
      version = 2;
      return true;
    case KEY2Token::VERSION_STR_3 :
      version = 3;
      return true;
    case KEY2Token::VERSION_STR_4 :
      version = 4;
      return true;
    case KEY2Token::VERSION_STR_5 :
      version = 5;
      return true;
    }
  }

  return false;
}

bool probeKeynoteXML(const RVNGInputStreamPtr_t &input, unsigned &version, xmlTextReaderPtr reader)
{
  if (probeKeynote2XML(input, version, reader))
    return true;

  input->seek(0, RVNG_SEEK_SET);

  return probeKeynote1XML(input, version, reader);
}

bool probeNumbersXML(const RVNGInputStreamPtr_t &input, unsigned &version, xmlTextReaderPtr reader)
{
  if (input->isEnd())
    return false;

  const IWORKTokenizer &tokenizer(NUM1Token::getTokenizer());
  assert(reader);

  const int id = tokenizer.getQualifiedId(char_cast(xmlTextReaderConstLocalName(reader)), char_cast(xmlTextReaderConstNamespaceUri(reader)));

  if ((NUM1Token::NS_URI_LS | NUM1Token::document) == id)
  {
    const std::string v = queryAttribute(reader, NUM1Token::version, NUM1Token::NS_URI_LS, tokenizer);

    switch (tokenizer.getId(v.c_str()))
    {
    case NUM1Token::VERSION_STR_2 :
      version = 2;
      return true;
    }
  }

  return false;
}

bool probePagesXML(const RVNGInputStreamPtr_t &input, unsigned &version, xmlTextReaderPtr reader)
{
  if (input->isEnd())
    return false;

  const IWORKTokenizer &tokenizer(PAG1Token::getTokenizer());
  assert(reader);

  const int id = tokenizer.getQualifiedId(char_cast(xmlTextReaderConstLocalName(reader)), char_cast(xmlTextReaderConstNamespaceUri(reader)));

  if ((PAG1Token::NS_URI_SL | PAG1Token::document) == id)
  {
    const std::string v = queryAttribute(reader, PAG1Token::version, PAG1Token::NS_URI_SL, tokenizer);

    switch (tokenizer.getId(v.c_str()))
    {
    case PAG1Token::VERSION_STR_4 :
      version = 4;
      return true;
    }
  }

  return false;
}

bool probeXMLImpl(const RVNGInputStreamPtr_t &input, const ProbeXMLFun_t probe, const EtonyekDocument::Type type, DetectionInfo &info)
{
  const shared_ptr<xmlTextReader> reader(xmlReaderForIO(readFromStream, closeStream, input.get(), "", 0, 0), xmlFreeTextReader);
  if (!reader)
    return false;

  int ret = 0;
  do
  {
    ret = xmlTextReaderRead(reader.get());
  }
  while ((1 == ret) && (XML_READER_TYPE_ELEMENT != xmlTextReaderNodeType(reader.get())));

  if (1 != ret)
    return false;

  if (probe(input, info.version, reader.get()))
  {
    info.type = type;
    return true;
  }

  return false;
}

bool probeXML(const ProbeXMLFun_t probe, const EtonyekDocument::Type type, tribool &isGzipped, DetectionInfo &info)
{
  if (isGzipped || indeterminate(isGzipped))
  {
    try
    {
      const RVNGInputStreamPtr_t uncompressed(new IWORKZlibStream(info.input));
      isGzipped = true;

      if (probeXMLImpl(uncompressed, probe, type, info))
      {
        info.input = uncompressed;
        return true;
      }
      else
      {
        return false; // compressed, but invalid format
      }
    }
    catch (...)
    {
      if (isGzipped) // decompression failed? most probably a broken file...
        return false;

      isGzipped = false;
    }

    info.input->seek(0, RVNG_SEEK_SET);
  }

  assert(isGzipped == false);

  return probeXMLImpl(info.input, probe, type, info);
}

bool detect(const RVNGInputStreamPtr_t &input, unsigned checkTypes, DetectionInfo &info)
{
  info.confidence = EtonyekDocument::CONFIDENCE_SUPPORTED_PART;
  bool isXML = true;
  tribool isGzipped = indeterminate;
  tribool isKeynote1 = indeterminate;

  if (input->isStructured())
  {
    info.package = input;

    // check which format it might be
    if (CHECK_TYPE_KEYNOTE & checkTypes)
    {
      if (input->existsSubStream("index.apxl"))
      {
        checkTypes = CHECK_TYPE_KEYNOTE;
        isGzipped = false;
        isKeynote1 = false;
        info.input.reset(input->getSubStreamByName("index.apxl"));
      }
      else if (input->existsSubStream("index.apxl.gz"))
      {
        checkTypes = CHECK_TYPE_KEYNOTE;
        isGzipped = true;
        isKeynote1 = false;
        info.input.reset(input->getSubStreamByName("index.apxl.gz"));
      }
      else if (input->existsSubStream("presentation.apxl"))
      {
        checkTypes = CHECK_TYPE_KEYNOTE;
        isGzipped = false;
        isKeynote1 = true;
        info.input.reset(input->getSubStreamByName("presentation.apxl"));
      }
      else if (input->existsSubStream("presentation.apxl.gz"))
      {
        checkTypes = CHECK_TYPE_KEYNOTE;
        isGzipped = true;
        isKeynote1 = true;
        info.input.reset(input->getSubStreamByName("presentation.apxl.gz"));
      }
    }

    if ((CHECK_TYPE_NUMBERS | CHECK_TYPE_PAGES) & checkTypes)
    {
      if (input->existsSubStream("index.xml"))
      {
        checkTypes &= (CHECK_TYPE_NUMBERS | CHECK_TYPE_PAGES);
        isGzipped = false;
        info.input.reset(input->getSubStreamByName("index.xml"));
      }
      else if (input->existsSubStream("index.xml.gz"))
      {
        checkTypes &= (CHECK_TYPE_NUMBERS | CHECK_TYPE_PAGES);
        isGzipped = false;
        info.input.reset(input->getSubStreamByName("index.xml.gz"));
      }
    }

    if (!info.input && (CHECK_TYPE_ANY & checkTypes))
    {
      if (input->existsSubStream("Index.zip"))
      {
        isXML = false;
        info.input.reset(input->getSubStreamByName("Index.zip"));
      }
    }

    if (!info.input)
    {
      // nothing detected
      // TODO: this might also be Index.zip...
      return EtonyekDocument::CONFIDENCE_NONE;
    }

    info.confidence = EtonyekDocument::CONFIDENCE_EXCELLENT; // this is either a valid package of a false positive
  }
  else
  {
    info.input = input;
  }

  assert(bool(info.input));

  if (isXML)
  {
    assert(CHECK_TYPE_ANY & checkTypes);
    assert(!info.input->isStructured());

    info.input->seek(0, RVNG_SEEK_SET);

    if (CHECK_TYPE_KEYNOTE & checkTypes)
    {
      const ProbeXMLFun_t probe = (isKeynote1 ? probeKeynote1XML : ((!isKeynote1) ? probeKeynote2XML : probeKeynoteXML));
      if (probeXML(probe, EtonyekDocument::TYPE_KEYNOTE, isGzipped, info))
        return true;

      info.input->seek(0, RVNG_SEEK_SET);
    }

    if (CHECK_TYPE_NUMBERS & checkTypes)
    {
      if (probeXML(probeNumbersXML, EtonyekDocument::TYPE_NUMBERS, isGzipped, info))
        return true;

      info.input->seek(0, RVNG_SEEK_SET);
    }

    if (CHECK_TYPE_PAGES & checkTypes)
    {
      if (probeXML(probePagesXML, EtonyekDocument::TYPE_PAGES, isGzipped, info))
        return true;
    }
  }
  else
  {
    // TODO: detect type in binary format
  }

  return EtonyekDocument::CONFIDENCE_NONE;
}

}

namespace
{

shared_ptr<IWORKParser> makeKeynoteParser(const unsigned version, const RVNGInputStreamPtr_t &input, const RVNGInputStreamPtr_t &package, KEYCollector *const collector, KEYDictionary &dict)
{
  shared_ptr<IWORKParser> parser;

  if (1 == version)
    parser.reset(new KEY1Parser(input, package, collector));
  else if ((2 <= version) && (5 >= version))
    parser.reset(new KEY2Parser(input, package, collector, dict));
  else
    assert(0);

  return parser;
}

}

ETONYEKAPI EtonyekDocument::Confidence EtonyekDocument::isSupported(librevenge::RVNGInputStream *const input, EtonyekDocument::Type *type) try
{
  if (!input)
    return CONFIDENCE_NONE;

  if (type)
    *type = TYPE_UNKNOWN;

  DetectionInfo info;

  if (detect(RVNGInputStreamPtr_t(input, EtonyekDummyDeleter()), CHECK_TYPE_ANY, info))
  {
    assert(TYPE_UNKNOWN != info.type);
    assert(CONFIDENCE_NONE != info.confidence);
    assert(bool(info.input));

    if (type)
      *type = info.type;
    return info.confidence;
  }

  return CONFIDENCE_NONE;
}
catch (...)
{
  return CONFIDENCE_NONE;
}

ETONYEKAPI bool EtonyekDocument::parse(librevenge::RVNGInputStream *const input, librevenge::RVNGPresentationInterface *const generator) try
{
  if (!input || !generator)
    return false;

  DetectionInfo info;

  if (!detect(RVNGInputStreamPtr_t(input, EtonyekDummyDeleter()), CHECK_TYPE_KEYNOTE, info))
    return false;

  assert(TYPE_UNKNOWN != info.type);
  assert(CONFIDENCE_NONE != info.confidence);
  assert(bool(info.input));
  assert(0 != info.version);

  info.input->seek(0, librevenge::RVNG_SEEK_SET);

  KEYDictionary dict;
  IWORKPresentationRedirector redirector(generator);
  KEYCollector collector(&redirector);
  const shared_ptr<IWORKParser> parser = makeKeynoteParser(info.version, info.input, info.package, &collector, dict);
  return parser->parse();
}
catch (...)
{
  return false;
}

ETONYEKAPI bool EtonyekDocument::parse(librevenge::RVNGInputStream *const input, librevenge::RVNGSpreadsheetInterface *const document) try
{
  if (!input || !document)
    return false;

  DetectionInfo info;

  if (!detect(RVNGInputStreamPtr_t(input, EtonyekDummyDeleter()), CHECK_TYPE_NUMBERS, info))
    return false;

  assert(TYPE_UNKNOWN != info.type);
  assert(CONFIDENCE_NONE != info.confidence);
  assert(bool(info.input));
  assert(0 != info.version);

  info.input->seek(0, librevenge::RVNG_SEEK_SET);

  IWORKSpreadsheetRedirector redirector(document);
  NUMCollector collector(&redirector);
  NUMDictionary dict;
  NUM1Parser parser(info.input, info.package, &collector, &dict);
  return parser.parse();
}
catch (...)
{
  return false;
}

ETONYEKAPI bool EtonyekDocument::parse(librevenge::RVNGInputStream *const input, librevenge::RVNGTextInterface *const document) try
{
  if (!input || !document)
    return false;

  DetectionInfo info;

  if (!detect(RVNGInputStreamPtr_t(input, EtonyekDummyDeleter()), CHECK_TYPE_PAGES, info))
    return false;

  assert(TYPE_UNKNOWN != info.type);
  assert(CONFIDENCE_NONE != info.confidence);
  assert(bool(info.input));
  assert(0 != info.version);

  info.input->seek(0, librevenge::RVNG_SEEK_SET);

  IWORKTextRedirector redirector(document);
  PAGCollector collector(&redirector);
  PAGDictionary dict;
  PAG1Parser parser(info.input, info.package, &collector, &dict);
  return parser.parse();
}
catch (...)
{
  return false;
}

}

/* vim:set shiftwidth=2 softtabstop=2 expandtab: */
<|endoftext|>


<commit_before>/**
 * @file llstatemachine.cpp
 * @brief LLStateMachine implementation file.
 *
 * $LicenseInfo:firstyear=2001&license=viewergpl$
 *
 * Copyright (c) 2001-2009, Linden Research, Inc.
 *
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 *
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at
 * http://secondlifegrid.net/programs/open_source/licensing/flossexception
 *
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 *
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

#include "linden_common.h"

#include "llstatemachine.h"
#include "llapr.h"

#define FSM_PRINT_STATE_TRANSITIONS (0)

U32 LLUniqueID::sNextID = 0;

bool	operator==(const LLUniqueID &a, const LLUniqueID &b)
{
	return (a.mId == b.mId);
}

bool	operator!=(const LLUniqueID &a, const LLUniqueID &b)
{
	return (a.mId != b.mId);
}

//-----------------------------------------------------------------------------
// LLStateDiagram
//-----------------------------------------------------------------------------
LLStateDiagram::LLStateDiagram()
{
	mUseDefaultState = FALSE;
}

LLStateDiagram::~LLStateDiagram()
{

}

// add a state to the state graph
BOOL LLStateDiagram::addState(LLFSMState *state)
{
	mStates[state] = Transitions();
	return TRUE;
}

// add a directed transition between 2 states
BOOL LLStateDiagram::addTransition(LLFSMState& start_state, LLFSMState& end_state, LLFSMTransition& transition)
{
	StateMap::iterator state_it;
	state_it = mStates.find(&start_state);
	Transitions* state_transitions = NULL;
	if (state_it == mStates.end() )
	{
		addState(&start_state);
		state_transitions = &mStates[&start_state];
	}
	else
	{
		state_transitions = &state_it->second;
	}
	state_it = mStates.find(&end_state);
	if (state_it == mStates.end() )
	{
		addState(&end_state);
	}

	Transitions::iterator transition_it = state_transitions->find(&transition);
	if (transition_it != state_transitions->end())
	{
		llerrs << "LLStateTable::addDirectedTransition() : transition already exists" << llendl;
		return FALSE; // transition already exists
	}

	(*state_transitions)[&transition] = &end_state;
	return TRUE;
}

// add an undirected transition between 2 states
BOOL LLStateDiagram::addUndirectedTransition(LLFSMState& start_state, LLFSMState& end_state, LLFSMTransition& transition)
{
	BOOL result;
	result = addTransition(start_state, end_state, transition);
	if (result)
	{
		result = addTransition(end_state, start_state, transition);
	}
	return result;
}

// add a transition that exists for every state
void LLStateDiagram::addDefaultTransition(LLFSMState& end_state, LLFSMTransition& transition)
{
	mDefaultTransitions[&transition] = &end_state;
}

// process a possible transition, and get the resulting state
LLFSMState* LLStateDiagram::processTransition(LLFSMState& start_state, LLFSMTransition& transition)
{
	// look up transition
	//LLFSMState** dest_state = (mStates.getValue(&start_state))->getValue(&transition);
	LLFSMState* dest_state = NULL;
	StateMap::iterator state_it = mStates.find(&start_state);
	if (state_it == mStates.end())
	{
		return NULL;
	}
	Transitions::iterator transition_it = state_it->second.find(&transition);

	// try default transitions if state-specific transition not found
	if (transition_it == state_it->second.end())
	{
		dest_state = mDefaultTransitions[&transition];
	}
	else
	{
		dest_state = transition_it->second;
	}

	// if we have a destination state...
	if (NULL != dest_state)
	{
		// ...return it...
		return dest_state;
	}
	// ... otherwise ...
	else
	{
		// ...look for default state...
		if (mUseDefaultState)
		{
			// ...return it if we have it...
			return mDefaultState;
		}
		else
		{
			// ...or else we're still in the same state.
			return &start_state;
		}
	}
}

void LLStateDiagram::setDefaultState(LLFSMState& default_state)
{
	mUseDefaultState = TRUE;
	mDefaultState = &default_state;
}

S32 LLStateDiagram::numDeadendStates()
{
	S32 numDeadends = 0;
	StateMap::iterator state_it;
	for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it)
	{
		if (state_it->second.size() == 0)
		{
			numDeadends++;
		}
	}
	return numDeadends;
}

BOOL LLStateDiagram::stateIsValid(LLFSMState& state)
{
	if (mStates.find(&state) != mStates.end())
	{
		return TRUE;
	}
	return FALSE;
}

LLFSMState* LLStateDiagram::getState(U32 state_id)
{
	StateMap::iterator state_it;
	for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it)
	{
		if (state_it->first->getID() == state_id)
		{
			return state_it->first;
		}
	}
	return NULL;
}

BOOL LLStateDiagram::saveDotFile(const std::string& filename)
{
	LLAPRFile outfile ;
	outfile.open(filename, LL_APR_W);
	apr_file_t* dot_file = outfile.getFileHandle() ;

	if (!dot_file)
	{
		llwarns << "LLStateDiagram::saveDotFile() : Couldn't open " << filename << " to save state diagram." << llendl;
		return FALSE;
	}
	apr_file_printf(dot_file, "digraph StateMachine {\n\tsize=\"100,100\";\n\tfontsize=40;\n\tlabel=\"Finite State Machine\";\n\torientation=landscape\n\tratio=.77\n");

	StateMap::iterator state_it;
	for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it)
	{
		apr_file_printf(dot_file, "\t\"%s\" [fontsize=28,shape=box]\n", state_it->first->getName().c_str());
	}
	apr_file_printf(dot_file, "\t\"All States\" [fontsize=30,style=bold,shape=box]\n");

	Transitions::iterator transitions_it;
	for(transitions_it = mDefaultTransitions.begin(); transitions_it != mDefaultTransitions.end(); ++transitions_it)
	{
		apr_file_printf(dot_file, "\t\"All States\" -> \"%s\" [label = \"%s\",fontsize=24];\n", transitions_it->second->getName().c_str(),
			transitions_it->second->getName().c_str());
	}

	if (mDefaultState)
	{
		apr_file_printf(dot_file, "\t\"All States\" -> \"%s\";\n", mDefaultState->getName().c_str());
	}

	for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it)
	{
		LLFSMState *state = state_it->first;

		Transitions::iterator transitions_it;
		for(transitions_it = state_it->second.begin();
			transitions_it != state_it->second.end();
			++transitions_it)
		{
			std::string state_name = state->getName();
			std::string target_name = transitions_it->second->getName();
			std::string transition_name = transitions_it->first->getName();
			apr_file_printf(dot_file, "\t\"%s\" -> \"%s\" [label = \"%s\",fontsize=24];\n", state->getName().c_str(),
				target_name.c_str(),
				transition_name.c_str());
		}
	}

	apr_file_printf(dot_file, "}\n");

	return TRUE;
}

std::ostream& operator<<(std::ostream &s, LLStateDiagram &FSM)
{
	if (FSM.mDefaultState)
	{
		s << "Default State: " << FSM.mDefaultState->getName() << "\n";
	}

	LLStateDiagram::Transitions::iterator transitions_it;
	for(transitions_it = FSM.mDefaultTransitions.begin();
		transitions_it != FSM.mDefaultTransitions.end();
		++transitions_it)
	{
		s << "Any State -- " << transitions_it->first->getName()
			<< " --> " << transitions_it->second->getName() << "\n";
	}

	LLStateDiagram::StateMap::iterator state_it;
	for(state_it = FSM.mStates.begin(); state_it != FSM.mStates.end(); ++state_it)
	{
		LLStateDiagram::Transitions::iterator transitions_it;
		for(transitions_it = state_it->second.begin();
			transitions_it != state_it->second.end();
			++transitions_it)
		{
			s << state_it->first->getName() << " -- " << transitions_it->first->getName()
				<< " --> " << transitions_it->second->getName() << "\n";
		}
		s << "\n";
	}

	return s;
}

//-----------------------------------------------------------------------------
// LLStateMachine
//-----------------------------------------------------------------------------

LLStateMachine::LLStateMachine()
{
	// we haven't received a starting state yet
	mCurrentState = NULL;
	mLastState = NULL;
	mLastTransition = NULL;
	mStateDiagram = NULL;
}

LLStateMachine::~LLStateMachine()
{

}

// returns current state
LLFSMState*	LLStateMachine::getCurrentState() const
{
	return mCurrentState;
}

// executes current state
void LLStateMachine::runCurrentState(void *data)
{
	mCurrentState->execute(data);
}

// set current state
BOOL LLStateMachine::setCurrentState(LLFSMState *initial_state, void* user_data, BOOL skip_entry)
{
	llassert(mStateDiagram);

	if (mStateDiagram->stateIsValid(*initial_state))
	{
		mLastState = mCurrentState = initial_state;
		if (!skip_entry)
		{
			initial_state->onEntry(user_data);
		}
		return TRUE;
	}

	return FALSE;
}

BOOL LLStateMachine::setCurrentState(U32 state_id, void* user_data, BOOL skip_entry)
{
	llassert(mStateDiagram);

	LLFSMState* state = mStateDiagram->getState(state_id);

	if (state)
	{
		mLastState = mCurrentState = state;
		if (!skip_entry)
		{
			state->onEntry(user_data);
		}
		return TRUE;
	}

	return FALSE;
}

void LLStateMachine::processTransition(LLFSMTransition& transition, void* user_data)
{
	llassert(mStateDiagram);

	if (NULL == mCurrentState)
	{
		llwarns << "mCurrentState == NULL; aborting processTransition()" << llendl;
		return;
	}

	LLFSMState* new_state = mStateDiagram->processTransition(*mCurrentState, transition);

	if (NULL == new_state)
	{
		llwarns << "new_state == NULL; aborting processTransition()" << llendl;
		return;
	}

	mLastTransition = &transition;
	mLastState = mCurrentState;

	if (*mCurrentState != *new_state)
	{
		mCurrentState->onExit(user_data);
		mCurrentState = new_state;
		mCurrentState->onEntry(user_data);
#if FSM_PRINT_STATE_TRANSITIONS
		llinfos << "Entering state " << mCurrentState->getName() <<
			" on transition " << transition.getName() << " from state " <<
			mLastState->getName() << llendl;
#endif
	}
}

void LLStateMachine::setStateDiagram(LLStateDiagram* diagram)
{
	mStateDiagram = diagram;
}
<commit_msg>CID-333<commit_after>/**
 * @file llstatemachine.cpp
 * @brief LLStateMachine implementation file.
 *
 * $LicenseInfo:firstyear=2001&license=viewergpl$
 *
 * Copyright (c) 2001-2009, Linden Research, Inc.
 *
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 *
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at
 * http://secondlifegrid.net/programs/open_source/licensing/flossexception
 *
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 *
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

#include "linden_common.h"

#include "llstatemachine.h"
#include "llapr.h"

#define FSM_PRINT_STATE_TRANSITIONS (0)

U32 LLUniqueID::sNextID = 0;

bool	operator==(const LLUniqueID &a, const LLUniqueID &b)
{
	return (a.mId == b.mId);
}

bool	operator!=(const LLUniqueID &a, const LLUniqueID &b)
{
	return (a.mId != b.mId);
}

//-----------------------------------------------------------------------------
// LLStateDiagram
//-----------------------------------------------------------------------------
LLStateDiagram::LLStateDiagram()
{
	mDefaultState = NULL;
	mUseDefaultState = FALSE;
}

LLStateDiagram::~LLStateDiagram()
{

}

// add a state to the state graph
BOOL LLStateDiagram::addState(LLFSMState *state)
{
	mStates[state] = Transitions();
	return TRUE;
}

// add a directed transition between 2 states
BOOL LLStateDiagram::addTransition(LLFSMState& start_state, LLFSMState& end_state, LLFSMTransition& transition)
{
	StateMap::iterator state_it;
	state_it = mStates.find(&start_state);
	Transitions* state_transitions = NULL;
	if (state_it == mStates.end() )
	{
		addState(&start_state);
		state_transitions = &mStates[&start_state];
	}
	else
	{
		state_transitions = &state_it->second;
	}
	state_it = mStates.find(&end_state);
	if (state_it == mStates.end() )
	{
		addState(&end_state);
	}

	Transitions::iterator transition_it = state_transitions->find(&transition);
	if (transition_it != state_transitions->end())
	{
		llerrs << "LLStateTable::addDirectedTransition() : transition already exists" << llendl;
		return FALSE; // transition already exists
	}

	(*state_transitions)[&transition] = &end_state;
	return TRUE;
}

// add an undirected transition between 2 states
BOOL LLStateDiagram::addUndirectedTransition(LLFSMState& start_state, LLFSMState& end_state, LLFSMTransition& transition)
{
	BOOL result;
	result = addTransition(start_state, end_state, transition);
	if (result)
	{
		result = addTransition(end_state, start_state, transition);
	}
	return result;
}

// add a transition that exists for every state
void LLStateDiagram::addDefaultTransition(LLFSMState& end_state, LLFSMTransition& transition)
{
	mDefaultTransitions[&transition] = &end_state;
}

// process a possible transition, and get the resulting state
LLFSMState* LLStateDiagram::processTransition(LLFSMState& start_state, LLFSMTransition& transition)
{
	// look up transition
	//LLFSMState** dest_state = (mStates.getValue(&start_state))->getValue(&transition);
	LLFSMState* dest_state = NULL;
	StateMap::iterator state_it = mStates.find(&start_state);
	if (state_it == mStates.end())
	{
		return NULL;
	}
	Transitions::iterator transition_it = state_it->second.find(&transition);

	// try default transitions if state-specific transition not found
	if (transition_it == state_it->second.end())
	{
		dest_state = mDefaultTransitions[&transition];
	}
	else
	{
		dest_state = transition_it->second;
	}

	// if we have a destination state...
	if (NULL != dest_state)
	{
		// ...return it...
		return dest_state;
	}
	// ... otherwise ...
	else
	{
		// ...look for default state...
		if (mUseDefaultState)
		{
			// ...return it if we have it...
			return mDefaultState;
		}
		else
		{
			// ...or else we're still in the same state.
			return &start_state;
		}
	}
}

void LLStateDiagram::setDefaultState(LLFSMState& default_state)
{
	mUseDefaultState = TRUE;
	mDefaultState = &default_state;
}

S32 LLStateDiagram::numDeadendStates()
{
	S32 numDeadends = 0;
	StateMap::iterator state_it;
	for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it)
	{
		if (state_it->second.size() == 0)
		{
			numDeadends++;
		}
	}
	return numDeadends;
}

BOOL LLStateDiagram::stateIsValid(LLFSMState& state)
{
	if (mStates.find(&state) != mStates.end())
	{
		return TRUE;
	}
	return FALSE;
}

LLFSMState* LLStateDiagram::getState(U32 state_id)
{
	StateMap::iterator state_it;
	for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it)
	{
		if (state_it->first->getID() == state_id)
		{
			return state_it->first;
		}
	}
	return NULL;
}

BOOL LLStateDiagram::saveDotFile(const std::string& filename)
{
	LLAPRFile outfile ;
	outfile.open(filename, LL_APR_W);
	apr_file_t* dot_file = outfile.getFileHandle() ;

	if (!dot_file)
	{
		llwarns << "LLStateDiagram::saveDotFile() : Couldn't open " << filename << " to save state diagram." << llendl;
		return FALSE;
	}
	apr_file_printf(dot_file, "digraph StateMachine {\n\tsize=\"100,100\";\n\tfontsize=40;\n\tlabel=\"Finite State Machine\";\n\torientation=landscape\n\tratio=.77\n");

	StateMap::iterator state_it;
	for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it)
	{
		apr_file_printf(dot_file, "\t\"%s\" [fontsize=28,shape=box]\n", state_it->first->getName().c_str());
	}
	apr_file_printf(dot_file, "\t\"All States\" [fontsize=30,style=bold,shape=box]\n");

	Transitions::iterator transitions_it;
	for(transitions_it = mDefaultTransitions.begin(); transitions_it != mDefaultTransitions.end(); ++transitions_it)
	{
		apr_file_printf(dot_file, "\t\"All States\" -> \"%s\" [label = \"%s\",fontsize=24];\n", transitions_it->second->getName().c_str(),
			transitions_it->second->getName().c_str());
	}

	if (mDefaultState)
	{
		apr_file_printf(dot_file, "\t\"All States\" -> \"%s\";\n", mDefaultState->getName().c_str());
	}

	for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it)
	{
		LLFSMState *state = state_it->first;

		Transitions::iterator transitions_it;
		for(transitions_it = state_it->second.begin();
			transitions_it != state_it->second.end();
			++transitions_it)
		{
			std::string state_name = state->getName();
			std::string target_name = transitions_it->second->getName();
			std::string transition_name = transitions_it->first->getName();
			apr_file_printf(dot_file, "\t\"%s\" -> \"%s\" [label = \"%s\",fontsize=24];\n", state->getName().c_str(),
				target_name.c_str(),
				transition_name.c_str());
		}
	}

	apr_file_printf(dot_file, "}\n");

	return TRUE;
}

std::ostream& operator<<(std::ostream &s, LLStateDiagram &FSM)
{
	if (FSM.mDefaultState)
	{
		s << "Default State: " << FSM.mDefaultState->getName() << "\n";
	}

	LLStateDiagram::Transitions::iterator transitions_it;
	for(transitions_it = FSM.mDefaultTransitions.begin();
		transitions_it != FSM.mDefaultTransitions.end();
		++transitions_it)
	{
		s << "Any State -- " << transitions_it->first->getName()
			<< " --> " << transitions_it->second->getName() << "\n";
	}

	LLStateDiagram::StateMap::iterator state_it;
	for(state_it = FSM.mStates.begin(); state_it != FSM.mStates.end(); ++state_it)
	{
		LLStateDiagram::Transitions::iterator transitions_it;
		for(transitions_it = state_it->second.begin();
			transitions_it != state_it->second.end();
			++transitions_it)
		{
			s << state_it->first->getName() << " -- " << transitions_it->first->getName()
				<< " --> " << transitions_it->second->getName() << "\n";
		}
		s << "\n";
	}

	return s;
}

//-----------------------------------------------------------------------------
// LLStateMachine
//-----------------------------------------------------------------------------

LLStateMachine::LLStateMachine()
{
	// we haven't received a starting state yet
	mCurrentState = NULL;
	mLastState = NULL;
	mLastTransition = NULL;
	mStateDiagram = NULL;
}

LLStateMachine::~LLStateMachine()
{

}

// returns current state
LLFSMState*	LLStateMachine::getCurrentState() const
{
	return mCurrentState;
}

// executes current state
void LLStateMachine::runCurrentState(void *data)
{
	mCurrentState->execute(data);
}

// set current state
BOOL LLStateMachine::setCurrentState(LLFSMState *initial_state, void* user_data, BOOL skip_entry)
{
	llassert(mStateDiagram);

	if (mStateDiagram->stateIsValid(*initial_state))
	{
		mLastState = mCurrentState = initial_state;
		if (!skip_entry)
		{
			initial_state->onEntry(user_data);
		}
		return TRUE;
	}

	return FALSE;
}

BOOL LLStateMachine::setCurrentState(U32 state_id, void* user_data, BOOL skip_entry)
{
	llassert(mStateDiagram);

	LLFSMState* state = mStateDiagram->getState(state_id);

	if (state)
	{
		mLastState = mCurrentState = state;
		if (!skip_entry)
		{
			state->onEntry(user_data);
		}
		return TRUE;
	}

	return FALSE;
}

void LLStateMachine::processTransition(LLFSMTransition& transition, void* user_data)
{
	llassert(mStateDiagram);

	if (NULL == mCurrentState)
	{
		llwarns << "mCurrentState == NULL; aborting processTransition()" << llendl;
		return;
	}

	LLFSMState* new_state = mStateDiagram->processTransition(*mCurrentState, transition);

	if (NULL == new_state)
	{
		llwarns << "new_state == NULL; aborting processTransition()" << llendl;
		return;
	}

	mLastTransition = &transition;
	mLastState = mCurrentState;

	if (*mCurrentState != *new_state)
	{
		mCurrentState->onExit(user_data);
		mCurrentState = new_state;
		mCurrentState->onEntry(user_data);
#if FSM_PRINT_STATE_TRANSITIONS
		llinfos << "Entering state " << mCurrentState->getName() <<
			" on transition " << transition.getName() << " from state " <<
			mLastState->getName() << llendl;
#endif
	}
}

void LLStateMachine::setStateDiagram(LLStateDiagram* diagram)
{
	mStateDiagram = diagram;
}
<|endoftext|>


<commit_before>/**
 * @file   lllinstancetracker.cpp
 *
 * $LicenseInfo:firstyear=2009&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

// Precompiled header
#include "linden_common.h"
// associated header
#include "llinstancetracker.h"
// STL headers
// std headers
// external library headers
// other Linden headers

<commit_msg>Trivial change to note that earlier changeset 27742 (1c3262183eb5) is a re-work of the LLInstanceTracker code done by Richard.    The commit comment is cryptic<commit_after>/**
 * @file   lllinstancetracker.cpp
 *
 * $LicenseInfo:firstyear=2009&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

// Precompiled header
#include "linden_common.h"
// associated header
#include "llinstancetracker.h"
// STL headers
// std headers
// external library headers
// other Linden headers

<|endoftext|>