// Copyright 2015 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/win/system_snapshot_win.h"

#include <windows.h>
#include <intrin.h>
#include <powrprof.h>
#include <winnt.h>

#include <algorithm>
#include <utility>
#include <vector>

#include "base/check_op.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "util/win/module_version.h"
#include "util/win/scoped_registry_key.h"

namespace crashpad {

namespace {

//! \brief Gets a string representation for a VS_FIXEDFILEINFO.dwFileFlags
//!     value.
std::string GetStringForFileFlags(uint32_t file_flags) {
  std::string result;
  DCHECK_EQ(file_flags & VS_FF_INFOINFERRED, 0u);
  if (file_flags & VS_FF_DEBUG)
    result += "Debug,";
  if (file_flags & VS_FF_PATCHED)
    result += "Patched,";
  if (file_flags & VS_FF_PRERELEASE)
    result += "Prerelease,";
  if (file_flags & VS_FF_PRIVATEBUILD)
    result += "Private,";
  if (file_flags & VS_FF_SPECIALBUILD)
    result += "Special,";
  if (!result.empty())
    return result.substr(0, result.size() - 1);  // Remove trailing comma.
  return result;
}

//! \brief Gets a string representation for a VS_FIXEDFILEINFO.dwFileOS value.
std::string GetStringForFileOS(uint32_t file_os) {
  // There are a variety of ancient things this could theoretically be. In
  // practice, we're always going to get VOS_NT_WINDOWS32 here.
  if ((file_os & VOS_NT_WINDOWS32) == VOS_NT_WINDOWS32)
    return "Windows NT";
  else
    return "Unknown";
}

}  // namespace

namespace internal {

SystemSnapshotWin::SystemSnapshotWin()
    : SystemSnapshot(),
      os_version_full_(),
      os_version_build_(),
      process_reader_(nullptr),
      os_version_major_(0),
      os_version_minor_(0),
      os_version_bugfix_(0),
      os_server_(false),
      initialized_() {}

SystemSnapshotWin::~SystemSnapshotWin() {}

void SystemSnapshotWin::Initialize(ProcessReaderWin* process_reader) {
  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);

  process_reader_ = process_reader;

  // We use both GetVersionEx() and GetModuleVersionAndType() (which uses
  // VerQueryValue() internally). GetVersionEx() is not trustworthy after
  // Windows 8 (depending on the application manifest) so its data is used only
  // to fill the os_server_ field, and the rest comes from the version
  // information stamped on kernel32.dll.
  OSVERSIONINFOEX version_info = {sizeof(version_info)};
  if (!GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&version_info))) {
    PLOG(WARNING) << "GetVersionEx";
  } else {
    os_server_ = version_info.wProductType != VER_NT_WORKSTATION;
  }

  static constexpr wchar_t kSystemDll[] = L"kernel32.dll";
  VS_FIXEDFILEINFO ffi;
  if (GetModuleVersionAndType(base::FilePath(kSystemDll), &ffi)) {
    std::string flags_string = GetStringForFileFlags(ffi.dwFileFlags);
    std::string os_name = GetStringForFileOS(ffi.dwFileOS);
    os_version_major_ = ffi.dwFileVersionMS >> 16;
    os_version_minor_ = ffi.dwFileVersionMS & 0xffff;
    os_version_bugfix_ = ffi.dwFileVersionLS >> 16;
    os_version_build_ = base::StringPrintf("%lu", ffi.dwFileVersionLS & 0xffff);
    os_version_full_ = base::StringPrintf(
        "%s %u.%u.%u.%s%s",
        os_name.c_str(),
        os_version_major_,
        os_version_minor_,
        os_version_bugfix_,
        os_version_build_.c_str(),
        flags_string.empty()
            ? ""
            : (std::string(" (") + flags_string + ")").c_str());
  }

  INITIALIZATION_STATE_SET_VALID(initialized_);
}

CPUArchitecture SystemSnapshotWin::GetCPUArchitecture() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

#if defined(ARCH_CPU_X86_FAMILY)
  return process_reader_->Is64Bit() ? kCPUArchitectureX86_64
                                    : kCPUArchitectureX86;
#elif defined(ARCH_CPU_ARM64)
  return kCPUArchitectureARM64;
#else
#error Unsupported Windows Arch
#endif
}

uint32_t SystemSnapshotWin::CPURevision() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

#if defined(ARCH_CPU_X86_FAMILY)
  uint32_t raw = CPUX86Signature();
  uint8_t stepping = raw & 0xf;
  uint8_t model = (raw & 0xf0) >> 4;
  uint8_t family = (raw & 0xf00) >> 8;
  uint8_t extended_model = static_cast<uint8_t>((raw & 0xf0000) >> 16);
  uint16_t extended_family = (raw & 0xff00000) >> 20;

  // For families before 15, extended_family are simply reserved bits.
  if (family < 15)
    extended_family = 0;
  // extended_model is only used for families 6 and 15.
  if (family != 6 && family != 15)
    extended_model = 0;

  uint16_t adjusted_family = family + extended_family;
  uint8_t adjusted_model = model + (extended_model << 4);
  return (adjusted_family << 16) | (adjusted_model << 8) | stepping;
#elif defined(ARCH_CPU_ARM64)
  SYSTEM_INFO system_info;
  GetSystemInfo(&system_info);

  return system_info.wProcessorRevision;
#else
#error Unsupported Windows Arch
#endif
}

uint8_t SystemSnapshotWin::CPUCount() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

  SYSTEM_INFO system_info;
  GetSystemInfo(&system_info);
  if (!base::IsValueInRangeForNumericType<uint8_t>(
          system_info.dwNumberOfProcessors)) {
    LOG(WARNING) << "dwNumberOfProcessors exceeds uint8_t storage";
  }
  return base::saturated_cast<uint8_t>(system_info.dwNumberOfProcessors);
}

std::string SystemSnapshotWin::CPUVendor() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

#if defined(ARCH_CPU_X86_FAMILY)
  int cpu_info[4];
  __cpuid(cpu_info, 0);
  char vendor[12];
  *reinterpret_cast<int*>(vendor) = cpu_info[1];
  *reinterpret_cast<int*>(vendor + 4) = cpu_info[3];
  *reinterpret_cast<int*>(vendor + 8) = cpu_info[2];
  return std::string(vendor, sizeof(vendor));
#elif defined(ARCH_CPU_ARM64)
  HKEY key;

  if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                   L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0",
                   0,
                   KEY_QUERY_VALUE,
                   &key) != ERROR_SUCCESS) {
    return std::string();
  }

  crashpad::ScopedRegistryKey scoped_key(key);
  DWORD type;
  wchar_t vendor_identifier[1024];
  DWORD vendor_identifier_size = sizeof(vendor_identifier);

  if (RegQueryValueEx(key,
                      L"VendorIdentifier",
                      nullptr,
                      &type,
                      reinterpret_cast<BYTE*>(vendor_identifier),
                      &vendor_identifier_size) != ERROR_SUCCESS ||
      type != REG_SZ) {
    return std::string();
  }

  std::string return_value;
  DCHECK_EQ(vendor_identifier_size % sizeof(wchar_t), 0u);
  base::UTF16ToUTF8(vendor_identifier,
                    vendor_identifier_size / sizeof(wchar_t),
                    &return_value);

  return return_value.c_str();
#else
#error Unsupported Windows Arch
#endif
}

void SystemSnapshotWin::CPUFrequency(uint64_t* current_hz,
                                     uint64_t* max_hz) const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

  int num_cpus = CPUCount();
  DCHECK_GT(num_cpus, 0);
  std::vector<PROCESSOR_POWER_INFORMATION> info(num_cpus);
  if (CallNtPowerInformation(ProcessorInformation,
                             nullptr,
                             0,
                             &info[0],
                             sizeof(PROCESSOR_POWER_INFORMATION) * num_cpus) !=
      0) {
    *current_hz = 0;
    *max_hz = 0;
    return;
  }
  constexpr uint64_t kMhzToHz = static_cast<uint64_t>(1E6);
  *current_hz = std::max_element(info.begin(),
                                 info.end(),
                                 [](const PROCESSOR_POWER_INFORMATION& a,
                                    const PROCESSOR_POWER_INFORMATION& b) {
                                   return a.CurrentMhz < b.CurrentMhz;
                                 })->CurrentMhz *
                kMhzToHz;
  *max_hz = std::max_element(info.begin(),
                             info.end(),
                             [](const PROCESSOR_POWER_INFORMATION& a,
                                const PROCESSOR_POWER_INFORMATION& b) {
                               return a.MaxMhz < b.MaxMhz;
                             })->MaxMhz *
            kMhzToHz;
}

uint32_t SystemSnapshotWin::CPUX86Signature() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

#if defined(ARCH_CPU_X86_FAMILY)
  int cpu_info[4];
  // We will never run on any processors that don't support at least function 1.
  __cpuid(cpu_info, 1);
  return cpu_info[0];
#else
  NOTREACHED();
  return 0;
#endif
}

uint64_t SystemSnapshotWin::CPUX86Features() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

#if defined(ARCH_CPU_X86_FAMILY)
  int cpu_info[4];
  // We will never run on any processors that don't support at least function 1.
  __cpuid(cpu_info, 1);
  return (static_cast<uint64_t>(cpu_info[2]) << 32) |
         static_cast<uint64_t>(cpu_info[3]);
#else
  NOTREACHED();
  return 0;
#endif
}

uint64_t SystemSnapshotWin::CPUX86ExtendedFeatures() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

#if defined(ARCH_CPU_X86_FAMILY)
  int cpu_info[4];
  // We will never run on any processors that don't support at least extended
  // function 1.
  __cpuid(cpu_info, 0x80000001);
  return (static_cast<uint64_t>(cpu_info[2]) << 32) |
         static_cast<uint64_t>(cpu_info[3]);
#else
  NOTREACHED();
  return 0;
#endif
}

uint32_t SystemSnapshotWin::CPUX86Leaf7Features() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

#if defined(ARCH_CPU_X86_FAMILY)
  int cpu_info[4];

  // Make sure leaf 7 can be called.
  __cpuid(cpu_info, 0);
  if (cpu_info[0] < 7)
    return 0;

  __cpuidex(cpu_info, 7, 0);
  return cpu_info[1];
#else
  NOTREACHED();
  return 0;
#endif
}

bool SystemSnapshotWin::CPUX86SupportsDAZ() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

#if defined(ARCH_CPU_X86_FAMILY)
  // The correct way to check for denormals-as-zeros (DAZ) support is to examine
  // mxcsr mask, which can be done with fxsave. See Intel Software Developer's
  // Manual, Volume 1: Basic Architecture (253665-051), 11.6.3 "Checking for the
  // DAZ Flag in the MXCSR Register". Note that since this function tests for
  // DAZ support in the CPU, it checks the mxcsr mask. Testing mxcsr would
  // indicate whether DAZ is actually enabled, which is a per-thread context
  // concern.

  // Test for fxsave support.
  uint64_t features = CPUX86Features();
  if (!(features & (UINT64_C(1) << 24))) {
    return false;
  }

  // Call fxsave.
  __declspec(align(16)) uint32_t extended_registers[128];
  _fxsave(&extended_registers);
  uint32_t mxcsr_mask = extended_registers[7];

  // Test the DAZ bit.
  return (mxcsr_mask & (1 << 6)) != 0;
#else
  NOTREACHED();
  return 0;
#endif
}

SystemSnapshot::OperatingSystem SystemSnapshotWin::GetOperatingSystem() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return kOperatingSystemWindows;
}

bool SystemSnapshotWin::OSServer() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return os_server_;
}

void SystemSnapshotWin::OSVersion(int* major,
                                  int* minor,
                                  int* bugfix,
                                  std::string* build) const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  *major = os_version_major_;
  *minor = os_version_minor_;
  *bugfix = os_version_bugfix_;
  build->assign(os_version_build_);
}

std::string SystemSnapshotWin::OSVersionFull() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return os_version_full_;
}

std::string SystemSnapshotWin::MachineDescription() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  // TODO(scottmg): Not sure if there's anything sensible to put here.
  return std::string();
}

bool SystemSnapshotWin::NXEnabled() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return !!IsProcessorFeaturePresent(PF_NX_ENABLED);
}

void SystemSnapshotWin::TimeZone(DaylightSavingTimeStatus* dst_status,
                                 int* standard_offset_seconds,
                                 int* daylight_offset_seconds,
                                 std::string* standard_name,
                                 std::string* daylight_name) const {
  // This returns the current time zone status rather than the status at the
  // time of the snapshot. This differs from the Mac implementation.
  TIME_ZONE_INFORMATION time_zone_information;
  *dst_status = static_cast<DaylightSavingTimeStatus>(
      GetTimeZoneInformation(&time_zone_information));
  *standard_offset_seconds =
      (time_zone_information.Bias + time_zone_information.StandardBias) * -60;
  *daylight_offset_seconds =
      (time_zone_information.Bias + time_zone_information.DaylightBias) * -60;
  *standard_name = base::WideToUTF8(time_zone_information.StandardName);
  *daylight_name = base::WideToUTF8(time_zone_information.DaylightName);
}

}  // namespace internal
}  // namespace crashpad
