/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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 "json_reader.h"

#include "pk11pub.h"

JsonReader::JsonReader(const std::string& n) : buf_(), available_(0), i_(0) {
  f_.reset(PR_Open(n.c_str(), PR_RDONLY, 00600));
  EXPECT_TRUE(f_) << "error opening vectors from: " << n;
  buf_[0] = 0;
}

uint64_t JsonReader::ReadInt() {
  SkipWhitespace();
  uint8_t c = peek();
  uint64_t v = 0;
  while (c >= '0' && c <= '9') {
    v = v * 10 + c - '0';
    next();
    c = peek();
  }
  return v;
}

// No input checking, no unicode, no escaping (not even \"), just read ASCII.
std::string JsonReader::ReadString() {
  SkipWhitespace();
  if (peek() != '"') {
    return "";
  }
  next();

  std::string s;
  uint8_t c = take();
  while (c != '"') {
    s.push_back(c);
    c = take();
  }
  return s;
}

std::string JsonReader::ReadLabel() {
  std::string s = ReadString();
  SkipWhitespace();
  EXPECT_EQ(take(), ':');
  return s;
}

std::vector<uint8_t> JsonReader::ReadHex() {
  SkipWhitespace();
  uint8_t c = take();
  EXPECT_EQ(c, '"');
  std::vector<uint8_t> v;
  c = take();
  while (c != '"') {
    v.push_back(JsonReader::Hex(c) << 4 | JsonReader::Hex(take()));
    c = take();
  }
  return v;
}

SECOidTag JsonReader::ReadHash() {
  std::string s = ReadString();
  if (s == "SHA-1") {
    return SEC_OID_SHA1;
  }
  if (s == "SHA-224") {
    return SEC_OID_SHA224;
  }
  if (s == "SHA-256") {
    return SEC_OID_SHA256;
  }
  if (s == "SHA-384") {
    return SEC_OID_SHA384;
  }
  if (s == "SHA-512") {
    return SEC_OID_SHA512;
  }
  ADD_FAILURE() << "unsupported hash";
  return SEC_OID_UNKNOWN;
}

bool JsonReader::NextItem(uint8_t h, uint8_t t) {
  SkipWhitespace();
  switch (uint8_t c = take()) {
    case ',':
      return true;
    case '{':
    case '[':
      EXPECT_EQ(c, h);
      SkipWhitespace();
      if (peek() == t) {
        next();
        return false;
      }
      return true;
    case '}':
    case ']':
      EXPECT_EQ(c, t);
      return false;
    default:
      ADD_FAILURE() << "Unexpected '" << c << "'";
  }
  return false;
}

void JsonReader::SkipValue() {
  SkipWhitespace();

  uint8_t c = take();
  if (c == '"') {
    do {
      c = take();
    } while (c != '"');

  } else if (c >= '0' && c <= '9') {
    c = peek();
    while (c >= '0' && c <= '9') {
      next();
      c = peek();
    }

  } else if (c == '[') {
    do {
      SkipWhitespace();
      if (peek() != ']') {
        SkipValue();
      }
    } while (NextItemArray());

  } else if (c == '{') {
    do {
      SkipWhitespace();
      if (peek() == '}') {
        continue;
      }

      std::string n = ReadLabel();
      if (n == "") {
        break;
      }
      SkipValue();
    } while (NextItem());

  } else {
    ADD_FAILURE() << "No idea how to skip '" << c << "'";
  }
}

void JsonReader::TopUp() {
  if (available_ > i_) {
    return;
  }
  i_ = 0;
  if (!f_) {
    return;
  }
  PRInt32 res = PR_Read(f_.get(), buf_, sizeof(buf_));
  if (res > 0) {
    available_ = static_cast<size_t>(res);
  } else {
    available_ = 1;
    f_.reset(nullptr);
    buf_[0] = 0;
  }
}

void JsonReader::SkipWhitespace() {
  uint8_t c = peek();
  while (c && (c == ' ' || c == '\t' || c == '\r' || c == '\n')) {
    next();
    c = peek();
  }
}

// This only handles lowercase.
uint8_t JsonReader::Hex(uint8_t c) {
  if (c >= '0' && c <= '9') {
    return c - '0';
  }
  EXPECT_TRUE(c >= 'a' && c <= 'f');
  return c - 'a' + 10;
}

extern std::string g_source_dir;

void WycheproofHeader(const std::string& name, const std::string& algorithm,
                      const std::string& schema,
                      std::function<void(JsonReader& r)> group_handler) {
  std::string basename = name + "_test.json";
  std::string dir = ::g_source_dir + "/../common/wycheproof/source_vectors/";
  std::cout << "Reading tests from: " << basename << std::endl;

  JsonReader r(dir + basename);
  while (r.NextItem()) {
    std::string n = r.ReadLabel();
    if (n == "") {
      break;
    }
    if (n == "algorithm") {
      ASSERT_EQ(algorithm, r.ReadString());
    } else if (n == "generatorVersion" || n == "numberOfTests") {
      r.SkipValue();
    } else if (n == "header") {
      while (r.NextItemArray()) {
        std::cout << "  " << r.ReadString() << std::endl;
      }
    } else if (n == "notes") {
      while (r.NextItem()) {
        std::string note = r.ReadLabel();
        if (note == "") {
          break;
        }
        std::cout << "    " << note << ": " << r.ReadString() << std::endl;
      }
    } else if (n == "schema") {
      ASSERT_EQ(schema, r.ReadString());
    } else if (n == "testGroups") {
      while (r.NextItemArray()) {
        group_handler(r);
      }
    } else {
      FAIL() << "unknown value in header";
    }
  }
}
