text: // Copyright 2021 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "src/sys/fuzzing/framework/engine/dictionary.h"
  
  #include <lib/syslog/cpp/macros.h>
  
  #include <iomanip>
  #include <sstream>
  
  #include <re2/re2.h>
  
  namespace fuzzing {
  
  // Helper for printing one byte as hexadecimal.
  static std::ostream& hex_byte(std::ostream& stream) {
    return stream << std::uppercase << std::setfill('0') << std::setw(2) << std::hex;
  }
  
  Dictionary& Dictionary::operator=(Dictionary&& other) noexcept {
    options_ = other.options_;
    other.options_ = nullptr;
  
    words_by_level_ = std::move(other.words_by_level_);
  
    max_level_ = other.max_level_;
    other.max_level_ = 0;
  
    return *this;
  }
  
  void Dictionary::AddDefaults(Options* options) {
    if (!options->has_dictionary_level()) {
      options->set_dictionary_level(kDefaultDictionaryLevel);
    }
  }
  
  void Dictionary::Configure(const OptionsPtr& options) { options_ = options; }
  
  void Dictionary::Add(const void* data, size_t size, uint16_t level) {
    const auto* bytes = reinterpret_cast<const uint8_t*>(data);
    Add(Word(bytes, bytes + size), level);
  }
  
  void Dictionary::Add(Word&& word, uint16_t level) {
    max_level_ = std::max(max_level_, level);
    words_by_level_[level].push_back(std::move(word));
  }
  
  bool Dictionary::Parse(const Input& input) {
    // TODO(fxbug.dev/89119): Support parsing utf8.
    re2::RE2 blank("^\\s*(?:#.*)?$");
    re2::RE2 value("^\\s*(?:\\w+(?:@(\\d+))?\\s*=)?\\s*\"(.*)$");
    const auto* c_str = reinterpret_cast<const char*>(input.data());
    std::istringstream iss(std::string(c_str, input.size()));
    std::string line;
    size_t line_no = 0;
    std::string remaining;
    while (std::getline(iss, line)) {
      line_no++;
      uint16_t level;
      Word word;
      // Skip blank lines and comment.
      if (re2::RE2::FullMatch(line, blank)) {
        continue;
      }
      // Use a default level of 0 if omitted.
      std::string level_str;
      std::string word_str;
      if (re2::RE2::FullMatch(line, value, &level_str, &word_str)) {
        if (!ParseLevel(level_str, &level)) {
          FX_LOGS(WARNING) << "failed to parse level: '" << level_str << "' (line " << line_no << ")";
          return false;
        }
        if (!ParseWord(word_str, &word, &remaining)) {
          FX_LOGS(WARNING) << "failed to parse word: '" << word_str << "' (line " << line_no << ")";
          return false;
        }
        if (!re2::RE2::FullMatch(remaining, blank)) {
          FX_LOGS(WARNING) << "failed to parse line: '" << line << "' (line " << line_no << ")";
          return false;
        }
      } else {
        FX_LOGS(WARNING) << "failed to parse line: '" << line << "' (line " << line_no << ")";
        return false;
      }
      Add(std::move(word), level);
    }
    return true;
  }
  
  bool Dictionary::ParseLevel(const std::string& str, uint16_t* out_level) {
    if (str.empty()) {
      *out_level = 0;
      return true;
    }
    return ParseNumber(str, 10, out_level);
  }
  
  bool Dictionary::ParseWord(const std::string& str, Word* out_word, std::string* out_remaining) {
    out_word->clear();
    bool escaped = false;
    uint8_t hex_byte = 0;
    for (size_t i = 0; i < str.size(); ++i) {
      char c = str[i];
      if (escaped) {
        switch (c) {
          case '"':
          case '\\':
            out_word->push_back(static_cast<uint8_t>(c));
            break;
          case 'x':
            if (i + 2 >= str.size() || !ParseNumber(str.substr(i, 2), 16, &hex_byte)) {
              return false;
            }
            out_word->push_back(hex_byte);
            i += 2;
            break;
          default:
            return false;
        }
        escaped = false;
      } else if (c == '"') {
        *out_remaining = str.substr(i + 1);
        return !out_word->empty();
      } else if (c == '\\') {
        escaped = true;
      } else if (isprint(c) || isspace(c)) {
        out_word->push_back(static_cast<uint8_t>(c));
      } else {
        FX_LOGS(WARNING) << "invalid byte: 0x" << hex_byte << c;
        return false;
      }
    }
    FX_LOGS(WARNING) << "missing '\"'";
    return false;
  }
  
  Input Dictionary::AsInput() const {
    std::ostringstream oss;
    size_t num_keys = 0;
    for (uint16_t level = 0; level <= max_level_; ++level) {
      const auto words = words_by_level_.find(level);
      if (words == words_by_level_.end()) {
        continue;
      }
      for (const auto& word : words->second) {
        oss << "key" << ++num_keys;
        if (level) {
          oss << "@" << level;
        }
        oss << "=\"";
        for (auto c : word) {
          if (c == '\\') {
            oss << "\\\\";
          } else if (c == '"') {
            oss << "\\\"";
          } else if (isprint(c) || isspace(c)) {
            oss << char(c);
          } else {
            oss << "\\x" << hex_byte << int(c);
            oss << std::dec;
          }
        }
        oss << "\"\n";
      }
    }
    return Input(oss.str());
  }
  
  void Dictionary::ForEachWord(fit::function<void(const uint8_t*, size_t)> func) const {
    FX_DCHECK(options_);
    for (uint16_t level = 0; level <= options_->dictionary_level(); ++level) {
      const auto words = words_by_level_.find(level);
      if (words == words_by_level_.end()) {
        continue;
      }
      for (const auto& word : words->second) {
        func(word.data(), word.size());
      }
    }
  }
  
  }  // namespace fuzzing
  