// Copyright 2020 The Crashpad Authors
//
// 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/ios/module_snapshot_ios_intermediate_dump.h"

#include <mach-o/loader.h>
#include <mach/mach.h>

#include "base/apple/mach_logging.h"
#include "base/files/file_path.h"
#include "client/annotation.h"
#include "snapshot/ios/intermediate_dump_reader_util.h"
#include "util/ios/ios_intermediate_dump_data.h"
#include "util/ios/ios_intermediate_dump_list.h"
#include "util/misc/from_pointer_cast.h"
#include "util/misc/uuid.h"

namespace crashpad {
namespace internal {

using Key = IntermediateDumpKey;

ModuleSnapshotIOSIntermediateDump::ModuleSnapshotIOSIntermediateDump()
    : ModuleSnapshot(),
      name_(),
      address_(0),
      size_(0),
      timestamp_(0),
      dylib_version_(0),
      source_version_(0),
      filetype_(0),
      initialized_() {}

ModuleSnapshotIOSIntermediateDump::~ModuleSnapshotIOSIntermediateDump() {}

bool ModuleSnapshotIOSIntermediateDump::Initialize(
    const IOSIntermediateDumpMap* image_data) {
  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);

  GetDataStringFromMap(image_data, Key::kName, &name_);
  GetDataValueFromMap(image_data, Key::kAddress, &address_);
  GetDataValueFromMap(image_data, Key::kSize, &size_);
  GetDataValueFromMap(image_data, Key::kFileType, &filetype_);

  // These keys are often missing.
  GetDataValueFromMap(image_data,
                      Key::kSourceVersion,
                      &source_version_,
                      LogMissingDataValueFromMap::kDontLogIfMissing);
  GetDataValueFromMap(image_data,
                      Key::kTimestamp,
                      &timestamp_,
                      LogMissingDataValueFromMap::kDontLogIfMissing);
  GetDataValueFromMap(image_data,
                      Key::kDylibCurrentVersion,
                      &dylib_version_,
                      LogMissingDataValueFromMap::kDontLogIfMissing);

  const IOSIntermediateDumpData* uuid_dump =
      GetDataFromMap(image_data, IntermediateDumpKey::kUUID);
  if (uuid_dump) {
    const std::vector<uint8_t>& bytes = uuid_dump->bytes();
    if (!bytes.data() || bytes.size() != 16) {
      LOG(ERROR) << "Invalid module uuid.";
    } else {
      uuid_.InitializeFromBytes(bytes.data());
    }
  }

  const IOSIntermediateDumpList* annotation_list =
      image_data->GetAsList(IntermediateDumpKey::kAnnotationObjects);
  if (annotation_list) {
    for (auto& annotation : *annotation_list) {
      std::string name;
      if (!GetDataStringFromMap(
              annotation.get(), Key::kAnnotationName, &name) ||
          name.empty() || name.length() > Annotation::kNameMaxLength) {
        LOG(ERROR) << "Invalid annotation name (" << name
                   << "), size=" << name.size()
                   << ", max size=" << Annotation::kNameMaxLength
                   << ", discarding annotation.";
        continue;
      }

      uint16_t type;
      const IOSIntermediateDumpData* type_dump =
          annotation->GetAsData(IntermediateDumpKey::kAnnotationType);
      const IOSIntermediateDumpData* value_dump =
          annotation->GetAsData(IntermediateDumpKey::kAnnotationValue);
      if (type_dump && value_dump && type_dump->GetValue<uint16_t>(&type)) {
        const std::vector<uint8_t>& bytes = value_dump->bytes();
        uint64_t length = bytes.size();
        if (!bytes.data() || length > Annotation::kValueMaxSize) {
          LOG(ERROR) << "Invalid annotation value, size=" << length
                     << ", max size=" << Annotation::kValueMaxSize
                     << ", discarding annotation.";
          continue;
        }
        annotation_objects_.push_back(AnnotationSnapshot(name, type, bytes));
      }
    }
  }

  const IOSIntermediateDumpList* simple_map_dump =
      image_data->GetAsList(IntermediateDumpKey::kAnnotationsSimpleMap);
  if (simple_map_dump) {
    for (auto& annotation : *simple_map_dump) {
      const IOSIntermediateDumpData* name_dump =
          annotation->GetAsData(IntermediateDumpKey::kAnnotationName);
      const IOSIntermediateDumpData* value_dump =
          annotation->GetAsData(IntermediateDumpKey::kAnnotationValue);
      if (name_dump && value_dump) {
        annotations_simple_map_.insert(
            make_pair(name_dump->GetString(), value_dump->GetString()));
      }
    }
  }

  const IOSIntermediateDumpList* extra_memory_regions_array =
      image_data->GetAsList(IntermediateDumpKey::kModuleExtraMemoryRegions);
  if (extra_memory_regions_array) {
    for (auto& region : *extra_memory_regions_array) {
      vm_address_t address;
      const IOSIntermediateDumpData* region_data =
          region->GetAsData(Key::kModuleExtraMemoryRegionData);
      if (!region_data)
        continue;
      if (GetDataValueFromMap(
              region.get(), Key::kModuleExtraMemoryRegionAddress, &address)) {
        const std::vector<uint8_t>& bytes = region_data->bytes();
        vm_size_t data_size = bytes.size();
        if (data_size == 0)
          continue;

        const vm_address_t data =
            reinterpret_cast<const vm_address_t>(bytes.data());

        auto memory =
            std::make_unique<internal::MemorySnapshotIOSIntermediateDump>();
        memory->Initialize(address, data, data_size);
        extra_memory_.push_back(std::move(memory));
      }
    }
  }

  const IOSIntermediateDumpList* intermediate_dump_extra_memory_regions_array =
      image_data->GetAsList(
          IntermediateDumpKey::kModuleIntermediateDumpExtraMemoryRegions);
  if (intermediate_dump_extra_memory_regions_array) {
    for (auto& region : *intermediate_dump_extra_memory_regions_array) {
      vm_address_t address;
      const IOSIntermediateDumpData* region_data =
          region->GetAsData(Key::kModuleIntermediateDumpExtraMemoryRegionData);
      if (!region_data)
        continue;
      if (GetDataValueFromMap(
              region.get(),
              Key::kModuleIntermediateDumpExtraMemoryRegionAddress,
              &address)) {
        const std::vector<uint8_t>& bytes = region_data->bytes();
        vm_size_t data_size = bytes.size();
        if (data_size == 0)
          continue;

        const vm_address_t data =
            reinterpret_cast<const vm_address_t>(bytes.data());

        auto memory =
            std::make_unique<internal::MemorySnapshotIOSIntermediateDump>();
        memory->Initialize(address, data, data_size);
        intermediate_dump_extra_memory_.push_back(std::move(memory));
      }
    }
  }

  const IOSIntermediateDumpMap* crash_info_dump =
      image_data->GetAsMap(IntermediateDumpKey::kAnnotationsCrashInfo);
  if (crash_info_dump) {
    const IOSIntermediateDumpData* message1_dump = crash_info_dump->GetAsData(
        IntermediateDumpKey::kAnnotationsCrashInfoMessage1);
    if (message1_dump) {
      std::string message1 = message1_dump->GetString();
      if (!message1.empty())
        annotations_vector_.push_back(message1);
    }
    const IOSIntermediateDumpData* message2_dump = crash_info_dump->GetAsData(
        IntermediateDumpKey::kAnnotationsCrashInfoMessage2);
    if (message2_dump) {
      std::string message2 = message2_dump->GetString();
      if (!message2.empty())
        annotations_vector_.push_back(message2);
    }
  }

  const IOSIntermediateDumpData* dyld_error_dump =
      image_data->GetAsData(IntermediateDumpKey::kAnnotationsDyldErrorString);
  if (dyld_error_dump) {
    std::string dyld_error_string = dyld_error_dump->GetString();
    if (!dyld_error_string.empty())
      annotations_vector_.push_back(dyld_error_string);
  }

  INITIALIZATION_STATE_SET_VALID(initialized_);
  return true;
}

std::string ModuleSnapshotIOSIntermediateDump::Name() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return name_;
}

uint64_t ModuleSnapshotIOSIntermediateDump::Address() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return address_;
}

uint64_t ModuleSnapshotIOSIntermediateDump::Size() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return size_;
}

time_t ModuleSnapshotIOSIntermediateDump::Timestamp() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return timestamp_;
}

void ModuleSnapshotIOSIntermediateDump::FileVersion(uint16_t* version_0,
                                                    uint16_t* version_1,
                                                    uint16_t* version_2,
                                                    uint16_t* version_3) const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  if (filetype_ == MH_DYLIB) {
    *version_0 = (dylib_version_ & 0xffff0000) >> 16;
    *version_1 = (dylib_version_ & 0x0000ff00) >> 8;
    *version_2 = (dylib_version_ & 0x000000ff);
    *version_3 = 0;
  } else {
    *version_0 = 0;
    *version_1 = 0;
    *version_2 = 0;
    *version_3 = 0;
  }
}

void ModuleSnapshotIOSIntermediateDump::SourceVersion(
    uint16_t* version_0,
    uint16_t* version_1,
    uint16_t* version_2,
    uint16_t* version_3) const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  *version_0 = (source_version_ & 0xffff000000000000u) >> 48;
  *version_1 = (source_version_ & 0x0000ffff00000000u) >> 32;
  *version_2 = (source_version_ & 0x00000000ffff0000u) >> 16;
  *version_3 = source_version_ & 0x000000000000ffffu;
}

ModuleSnapshot::ModuleType ModuleSnapshotIOSIntermediateDump::GetModuleType()
    const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  switch (filetype_) {
    case MH_EXECUTE:
      return kModuleTypeExecutable;
    case MH_DYLIB:
      return kModuleTypeSharedLibrary;
    case MH_DYLINKER:
      return kModuleTypeDynamicLoader;
    case MH_BUNDLE:
      return kModuleTypeLoadableModule;
    default:
      return kModuleTypeUnknown;
  }
}

void ModuleSnapshotIOSIntermediateDump::UUIDAndAge(crashpad::UUID* uuid,
                                                   uint32_t* age) const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  *uuid = uuid_;
  *age = 0;
}

std::string ModuleSnapshotIOSIntermediateDump::DebugFileName() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return base::FilePath(Name()).BaseName().value();
}

std::vector<uint8_t> ModuleSnapshotIOSIntermediateDump::BuildID() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return std::vector<uint8_t>();
}

std::vector<std::string> ModuleSnapshotIOSIntermediateDump::AnnotationsVector()
    const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return annotations_vector_;
}

std::map<std::string, std::string>
ModuleSnapshotIOSIntermediateDump::AnnotationsSimpleMap() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return annotations_simple_map_;
}

std::vector<AnnotationSnapshot>
ModuleSnapshotIOSIntermediateDump::AnnotationObjects() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return annotation_objects_;
}

std::set<CheckedRange<uint64_t>>
ModuleSnapshotIOSIntermediateDump::ExtraMemoryRanges() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return std::set<CheckedRange<uint64_t>>();
}

std::vector<const MemorySnapshot*>
ModuleSnapshotIOSIntermediateDump::ExtraMemory() const {
  std::vector<const MemorySnapshot*> extra_memory;
  for (const auto& memory : extra_memory_) {
    extra_memory.push_back(memory.get());
  }
  return extra_memory;
}

std::vector<const MemorySnapshot*>
ModuleSnapshotIOSIntermediateDump::IntermediateDumpExtraMemory() const {
  std::vector<const MemorySnapshot*> intermediate_dump_extra_memory;
  for (const auto& memory : intermediate_dump_extra_memory_) {
    intermediate_dump_extra_memory.push_back(memory.get());
  }
  return intermediate_dump_extra_memory;
}

std::vector<const UserMinidumpStream*>
ModuleSnapshotIOSIntermediateDump::CustomMinidumpStreams() const {
  return std::vector<const UserMinidumpStream*>();
}

}  // namespace internal
}  // namespace crashpad
