// Copyright 2015 The Chromium OS 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 "bsdiff/test_utils.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include <gtest/gtest.h>

using std::vector;

namespace {

// If |path| is absolute, or explicit relative to the current working directory,
// leaves it as is. Otherwise, if TMPDIR is defined in the environment and is
// non-empty, prepends it to |path|. Otherwise, prepends /tmp.  Returns the
// resulting path.
const std::string PrependTmpdir(const std::string& path) {
  if (path[0] == '/')
    return path;

  const char* tmpdir = getenv("TMPDIR");
  const std::string prefix = (tmpdir && *tmpdir ? tmpdir : "/tmp");
  return prefix + "/" + path;
}

bool MakeTempFile(const std::string& base_filename_template,
                  std::string* filename) {
  const std::string filename_template = PrependTmpdir(base_filename_template);
  vector<char> result(filename_template.size() + 1, '\0');
  memcpy(result.data(), filename_template.data(), filename_template.size());

  int mkstemp_fd = mkstemp(result.data());
  if (mkstemp_fd < 0) {
    PLOG(ERROR) << "mkstemp() Failed";
    return false;
  }
  close(mkstemp_fd);

  if (filename)
    *filename = result.data();
  return true;
}

}  // namespace

namespace test_utils {

void BsdiffTestEnvironment::SetUp() {
#ifdef BSDIFF_TARGET_UNITTEST
#define BSDIFF_TARGET_TMP_BASE "/data/tmp"
      if (access(BSDIFF_TARGET_TMP_BASE, F_OK) == -1) {
        mkdir(BSDIFF_TARGET_TMP_BASE, S_IRWXU | S_IRWXG | S_IROTH | S_IWOTH);
      }
      setenv("TMPDIR", BSDIFF_TARGET_TMP_BASE, 1);
#endif  // defined (BSDIFF_TARGET_UNITTEST)
}

bool ReadFile(const std::string& path, vector<uint8_t>* out) {
  FILE* fp = fopen(path.c_str(), "r");
  if (!fp)
    return false;
  out->clear();

  uint8_t buf[16 * 1024];
  while (true) {
    size_t bytes_read = fread(buf, 1, sizeof(buf), fp);
    if (!bytes_read)
      break;
    out->insert(out->end(), buf, buf + bytes_read);
  }
  bool result = !ferror(fp);
  fclose(fp);
  return result;
}

bool WriteFile(const std::string& path, vector<uint8_t> contents) {
  FILE* fp = fopen(path.c_str(), "r");
  if (!fp)
    return false;
  size_t written = fwrite(contents.data(), 1, contents.size(), fp);
  bool result = written == contents.size() && !ferror(fp);
  fclose(fp);
  return result;
}

ScopedTempFile::ScopedTempFile(const std::string& pattern) {
  EXPECT_TRUE(MakeTempFile(pattern, &filename_));
}

ScopedTempFile::~ScopedTempFile() {
  if (!filename_.empty() && unlink(filename_.c_str()) < 0) {
    PLOG(ERROR) << "Unable to remove temporary file.";
  }
}

bool BsdiffPatchFile::LoadFromFile(const std::string& filename) {
  vector<uint8_t> contents;
  if (!ReadFile(filename, &contents))
    return false;
  file_size = contents.size();
  // Check that the file includes at least the header.
  TEST_AND_RETURN_FALSE(contents.size() >= kHeaderSize);
  magic = std::string(contents.data(), contents.data() + 8);
  memcpy(&ctrl_len, contents.data() + 8, sizeof(ctrl_len));
  memcpy(&diff_len, contents.data() + 16, sizeof(diff_len));
  memcpy(&new_file_len, contents.data() + 24, sizeof(new_file_len));

  // Sanity check before we attempt to parse the bz2 streams.
  TEST_AND_RETURN_FALSE(ctrl_len >= 0);
  TEST_AND_RETURN_FALSE(diff_len >= 0);

  // The cast is safe since ctrl_len and diff_len are both positive.
  TEST_AND_RETURN_FALSE(file_size >=
        static_cast<uint64_t>(kHeaderSize + ctrl_len + diff_len));
  extra_len = file_size - kHeaderSize - ctrl_len - diff_len;

  uint8_t* ptr = contents.data() + kHeaderSize;
  bz2_ctrl = vector<uint8_t>(ptr, ptr + ctrl_len);
  ptr += ctrl_len;
  bz2_diff = vector<uint8_t>(ptr, ptr + diff_len);
  ptr += diff_len;
  bz2_extra = vector<uint8_t>(ptr, ptr + extra_len);

  return true;
}

bool BsdiffPatchFile::IsValid() const {
  TEST_AND_RETURN_FALSE(ctrl_len >= 0);
  TEST_AND_RETURN_FALSE(diff_len >= 0);
  TEST_AND_RETURN_FALSE(new_file_len >= 0);

  // TODO(deymo): Test that the length of the decompressed bz2 streams |diff|
  // plus |extra| are equal to the |new_file_len|.
  // TODO(deymo): Test that all the |bz2_ctrl| triplets (x, y, z) have a "x"
  // and "y" value >= 0 ("z" can be negative).
  return true;
}

}  // namespace test_utils