// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "snapshot/crashpad_types/image_annotation_reader.h"

#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#include <algorithm>

#include "build/build_config.h"
#include "client/annotation.h"
#include "client/annotation_list.h"
#include "client/simple_string_dictionary.h"
#include "gtest/gtest.h"
#include "test/multiprocess_exec.h"
#include "test/process_type.h"
#include "util/file/file_io.h"
#include "util/misc/as_underlying_type.h"
#include "util/misc/from_pointer_cast.h"
#include "util/process/process_memory_native.h"

#if defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_CHROMEOS)
#include "test/linux/fake_ptrace_connection.h"
#endif

namespace crashpad {
namespace test {
namespace {

void ExpectSimpleMap(const std::map<std::string, std::string>& map,
                     const SimpleStringDictionary& expected_map) {
  EXPECT_EQ(map.size(), expected_map.GetCount());
  for (const auto& pair : map) {
    EXPECT_EQ(pair.second, expected_map.GetValueForKey(pair.first));
  }
}

void ExpectAnnotationList(const std::vector<AnnotationSnapshot>& list,
                          AnnotationList& expected_list) {
  size_t index = 0;
  for (const Annotation* expected_annotation : expected_list) {
    const AnnotationSnapshot& annotation = list[index++];
    EXPECT_EQ(annotation.name, expected_annotation->name());
    EXPECT_EQ(annotation.type, AsUnderlyingType(expected_annotation->type()));
    EXPECT_EQ(annotation.value.size(), expected_annotation->size());
    EXPECT_EQ(memcmp(annotation.value.data(),
                     expected_annotation->value(),
                     std::min(VMSize{annotation.value.size()},
                              VMSize{expected_annotation->size()})),
              0);
  }
}

void BuildTestStructures(
    std::vector<std::unique_ptr<Annotation>>* annotations_storage,
    SimpleStringDictionary* into_map,
    AnnotationList* into_annotation_list) {
  into_map->SetKeyValue("key", "value");
  into_map->SetKeyValue("key2", "value2");

  static constexpr char kAnnotationName[] = "test annotation";
  static constexpr char kAnnotationValue[] = "test annotation value";
  annotations_storage->push_back(std::make_unique<Annotation>(
      Annotation::Type::kString,
      kAnnotationName,
      reinterpret_cast<void*>(const_cast<char*>(kAnnotationValue))));
  annotations_storage->back()->SetSize(sizeof(kAnnotationValue));
  into_annotation_list->Add(annotations_storage->back().get());

  static constexpr char kAnnotationName2[] = "test annotation2";
  static constexpr char kAnnotationValue2[] = "test annotation value2";
  annotations_storage->push_back(std::make_unique<Annotation>(
      Annotation::Type::kString,
      kAnnotationName2,
      reinterpret_cast<void*>(const_cast<char*>(kAnnotationValue2))));
  annotations_storage->back()->SetSize(sizeof(kAnnotationValue2));
  into_annotation_list->Add(annotations_storage->back().get());
}

void ExpectAnnotations(ProcessType process,
                       bool is_64_bit,
                       VMAddress simple_map_address,
                       VMAddress annotation_list_address) {
#if defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_CHROMEOS)
  FakePtraceConnection connection;
  ASSERT_TRUE(connection.Initialize(process));
  ProcessMemoryLinux memory(&connection);
#else
  ProcessMemoryNative memory;
  ASSERT_TRUE(memory.Initialize(process));
#endif

  ProcessMemoryRange range;
  ASSERT_TRUE(range.Initialize(&memory, is_64_bit));

  SimpleStringDictionary expected_simple_map;
  std::vector<std::unique_ptr<Annotation>> storage;
  AnnotationList expected_annotations;
  BuildTestStructures(&storage, &expected_simple_map, &expected_annotations);

  ImageAnnotationReader reader(&range);

  std::map<std::string, std::string> simple_map;
  ASSERT_TRUE(reader.SimpleMap(simple_map_address, &simple_map));
  ExpectSimpleMap(simple_map, expected_simple_map);

  std::vector<AnnotationSnapshot> annotation_list;
  ASSERT_TRUE(
      reader.AnnotationsList(annotation_list_address, &annotation_list));
  ExpectAnnotationList(annotation_list, expected_annotations);
}

TEST(ImageAnnotationReader, ReadFromSelf) {
  SimpleStringDictionary map;
  std::vector<std::unique_ptr<Annotation>> storage;
  AnnotationList annotations;
  BuildTestStructures(&storage, &map, &annotations);

#if defined(ARCH_CPU_64_BITS)
  constexpr bool am_64_bit = true;
#else
  constexpr bool am_64_bit = false;
#endif

  ExpectAnnotations(GetSelfProcess(),
                    am_64_bit,
                    FromPointerCast<VMAddress>(&map),
                    FromPointerCast<VMAddress>(&annotations));
}

CRASHPAD_CHILD_TEST_MAIN(ReadAnnotationsFromChildTestMain) {
  SimpleStringDictionary map;
  std::vector<std::unique_ptr<Annotation>> storage;
  AnnotationList annotations;
  BuildTestStructures(&storage, &map, &annotations);

  VMAddress simple_map_address = FromPointerCast<VMAddress>(&map);
  VMAddress annotations_address = FromPointerCast<VMAddress>(&annotations);
  FileHandle out = StdioFileHandle(StdioStream::kStandardOutput);
  CheckedWriteFile(out, &simple_map_address, sizeof(simple_map_address));
  CheckedWriteFile(out, &annotations_address, sizeof(annotations_address));

  CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput));
  return 0;
}

class ReadFromChildTest : public MultiprocessExec {
 public:
  ReadFromChildTest() : MultiprocessExec() {
    SetChildTestMainFunction("ReadAnnotationsFromChildTestMain");
  }

  ReadFromChildTest(const ReadFromChildTest&) = delete;
  ReadFromChildTest& operator=(const ReadFromChildTest&) = delete;

  ~ReadFromChildTest() = default;

 private:
  void MultiprocessParent() {
#if defined(ARCH_CPU_64_BITS)
    constexpr bool am_64_bit = true;
#else
    constexpr bool am_64_bit = false;
#endif

    VMAddress simple_map_address;
    VMAddress annotations_address;
    ASSERT_TRUE(ReadFileExactly(
        ReadPipeHandle(), &simple_map_address, sizeof(simple_map_address)));
    ASSERT_TRUE(ReadFileExactly(
        ReadPipeHandle(), &annotations_address, sizeof(annotations_address)));
    ExpectAnnotations(
        ChildProcess(), am_64_bit, simple_map_address, annotations_address);
  }
};

TEST(ImageAnnotationReader, ReadFromChild) {
  ReadFromChildTest test;
  test.Run();
}

}  // namespace
}  // namespace test
}  // namespace crashpad
