chromium/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/policy/remote_commands/device_command_run_routine_job.h"

#include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>

#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/syslog_logging.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chromeos/ash/services/cros_healthd/public/cpp/service_connection.h"
#include "chromeos/ash/services/cros_healthd/public/mojom/nullable_primitives.mojom.h"
#include "components/policy/proto/device_management_backend.pb.h"

namespace policy {

namespace em = enterprise_management;

namespace {

// String constant identifying the id field in the result payload.
constexpr char kIdFieldName[] = "id";
// String constant identifying the status field in the result payload.
constexpr char kStatusFieldName[] = "status";

// String constant identifying the routine enum field in the command payload.
constexpr char kRoutineEnumFieldName[] = "routineEnum";
// String constant identifying the parameter dictionary field in the command
// payload.
constexpr char kParamsFieldName[] = "params";

// Returns a RunRoutineResponse with an id of kFailedToStartId and a status of
// kFailedToStart.
ash::cros_healthd::mojom::RunRoutineResponsePtr
MakeInvalidParametersResponse() {
  return ash::cros_healthd::mojom::RunRoutineResponse::New(
      ash::cros_healthd::mojom::kFailedToStartId,
      ash::cros_healthd::mojom::DiagnosticRoutineStatusEnum::kFailedToStart);
}

template <typename T>
bool PopulateMojoEnumValueIfValid(int possible_enum, T* valid_enum_out) {
  DCHECK(valid_enum_out);
  if (!base::IsValueInRangeForNumericType<
          typename std::underlying_type<T>::type>(possible_enum)) {
    return false;
  }
  T enum_to_check = static_cast<T>(possible_enum);
  if (!ash::cros_healthd::mojom::IsKnownEnumValue(enum_to_check)) {
    return false;
  }
  *valid_enum_out = enum_to_check;
  return true;
}

std::string CreatePayload(
    ash::cros_healthd::mojom::RunRoutineResponsePtr response) {
  auto root_dict =
      base::Value::Dict()
          .Set(kIdFieldName, response->id)
          .Set(kStatusFieldName, static_cast<int>(response->status));

  std::string payload;
  base::JSONWriter::Write(root_dict, &payload);
  return payload;
}
}  // namespace

// static
constexpr char DeviceCommandRunRoutineJob::kStunServerHostnameFieldName[];

DeviceCommandRunRoutineJob::DeviceCommandRunRoutineJob() = default;

DeviceCommandRunRoutineJob::~DeviceCommandRunRoutineJob() = default;

em::RemoteCommand_Type DeviceCommandRunRoutineJob::GetType() const {
  return em::RemoteCommand_Type_DEVICE_RUN_DIAGNOSTIC_ROUTINE;
}

bool DeviceCommandRunRoutineJob::ParseCommandPayload(
    const std::string& command_payload) {
  std::optional<base::Value> root(base::JSONReader::Read(command_payload));
  if (!root.has_value()) {
    return false;
  }
  if (!root->is_dict()) {
    return false;
  }

  base::Value::Dict& dict = root->GetDict();
  // Make sure the command payload specified a valid DiagnosticRoutineEnum.
  std::optional<int> routine_enum = dict.FindInt(kRoutineEnumFieldName);
  if (!routine_enum.has_value()) {
    return false;
  }
  if (!PopulateMojoEnumValueIfValid(routine_enum.value(), &routine_enum_)) {
    SYSLOG(ERROR) << "Unknown DiagnosticRoutineEnum in command payload: "
                  << routine_enum.value();
    return false;
  }

  // Make sure there's a dictionary with parameter values for the routine.
  // Validation of routine-specific parameters will be done before running the
  // routine, so here we just check that any dictionary was given to us.
  auto* params_dict = dict.FindDict(kParamsFieldName);
  if (!params_dict) {
    return false;
  }
  params_dict_ = std::move(*params_dict);

  return true;
}

void DeviceCommandRunRoutineJob::RunImpl(CallbackWithResult result_callback) {
  SYSLOG(INFO) << "Executing RunRoutine command with DiagnosticRoutineEnum "
               << routine_enum_;
  auto* diagnostics_service =
      ash::cros_healthd::ServiceConnection::GetInstance()
          ->GetDiagnosticsService();

  switch (routine_enum_) {
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kUnknown: {
      NOTREACHED_IN_MIGRATION() << "This default value should not be used.";
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryCapacity: {
      diagnostics_service->RunBatteryCapacityRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryHealth: {
      diagnostics_service->RunBatteryHealthRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kUrandom: {
      constexpr char kLengthSecondsFieldName[] = "lengthSeconds";
      std::optional<int> length_seconds =
          params_dict_.FindInt(kLengthSecondsFieldName);
      ash::cros_healthd::mojom::NullableUint32Ptr routine_duration;
      if (length_seconds.has_value()) {
        // If the optional integer parameter is specified, it must be >= 0.
        int value = length_seconds.value();
        if (value < 0) {
          SYSLOG(ERROR) << "Invalid parameters for Urandom routine.";
          base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
              FROM_HERE,
              base::BindOnce(std::move(result_callback), ResultType::kFailure,
                             CreatePayload(MakeInvalidParametersResponse())));
          break;
        }
        routine_duration = ash::cros_healthd::mojom::NullableUint32::New(value);
      }
      diagnostics_service->RunUrandomRoutine(
          std::move(routine_duration),
          base::BindOnce(
              &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
              weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kSmartctlCheck: {
      diagnostics_service->RunSmartctlCheckRoutine(
          ash::cros_healthd::mojom::NullableUint32Ptr(),
          base::BindOnce(
              &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
              weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::
        kSmartctlCheckWithPercentageUsed: {
      constexpr char kPercentageUsedThresholdFieldName[] =
          "percentageUsedThreshold";
      std::optional<int> percentage_used_threshold =
          params_dict_.FindInt(kPercentageUsedThresholdFieldName);
      ash::cros_healthd::mojom::NullableUint32Ptr input_threshold;
      // The smartctl check routine expects one optional integer >= 0.
      if (percentage_used_threshold.has_value()) {
        // If the optional integer parameter is specified, it must be [0, 255].
        int value = percentage_used_threshold.value();
        if (value < 0 || value > 255) {
          SYSLOG(ERROR) << "Invalid parameters for smartctl check routine.";
          base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
              FROM_HERE,
              base::BindOnce(std::move(result_callback), ResultType::kFailure,
                             CreatePayload(MakeInvalidParametersResponse())));
          break;
        }
        input_threshold = ash::cros_healthd::mojom::NullableUint32::New(value);
      }
      diagnostics_service->RunSmartctlCheckRoutine(
          std::move(input_threshold),
          base::BindOnce(
              &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
              weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kAcPower: {
      constexpr char kExpectedStatusFieldName[] = "expectedStatus";
      // Note that expectedPowerType is an optional parameter.
      constexpr char kExpectedPowerTypeFieldName[] = "expectedPowerType";
      std::optional<int> expected_status =
          params_dict_.FindInt(kExpectedStatusFieldName);
      std::string* expected_power_type =
          params_dict_.FindString(kExpectedPowerTypeFieldName);
      ash::cros_healthd::mojom::AcPowerStatusEnum expected_status_enum;
      // The AC power routine expects a valid ACPowerStatusEnum, and optionally
      // a string.
      if (!expected_status.has_value() ||
          !PopulateMojoEnumValueIfValid(expected_status.value(),
                                        &expected_status_enum)) {
        SYSLOG(ERROR) << "Invalid parameters for AC Power routine.";
        base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE,
            base::BindOnce(std::move(result_callback), ResultType::kFailure,
                           CreatePayload(MakeInvalidParametersResponse())));
        break;
      }
      diagnostics_service->RunAcPowerRoutine(
          expected_status_enum,
          expected_power_type ? std::optional<std::string>(*expected_power_type)
                              : std::nullopt,
          base::BindOnce(
              &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
              weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kCpuCache: {
      constexpr char kLengthSecondsFieldName[] = "lengthSeconds";
      std::optional<int> length_seconds =
          params_dict_.FindInt(kLengthSecondsFieldName);
      ash::cros_healthd::mojom::NullableUint32Ptr routine_duration;
      if (length_seconds.has_value()) {
        // If the optional integer parameter is specified, it must be >= 0.
        int value = length_seconds.value();
        if (value < 0) {
          SYSLOG(ERROR) << "Invalid parameters for CPU cache routine.";
          base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
              FROM_HERE,
              base::BindOnce(std::move(result_callback), ResultType::kFailure,
                             CreatePayload(MakeInvalidParametersResponse())));
          break;
        }
        routine_duration = ash::cros_healthd::mojom::NullableUint32::New(value);
      }
      diagnostics_service->RunCpuCacheRoutine(
          std::move(routine_duration),
          base::BindOnce(
              &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
              weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kCpuStress: {
      constexpr char kLengthSecondsFieldName[] = "lengthSeconds";
      std::optional<int> length_seconds =
          params_dict_.FindInt(kLengthSecondsFieldName);
      ash::cros_healthd::mojom::NullableUint32Ptr routine_duration;
      if (length_seconds.has_value()) {
        // If the optional integer parameter is specified, it must be >= 0.
        int value = length_seconds.value();
        if (value < 0) {
          SYSLOG(ERROR) << "Invalid parameters for CPU stress routine.";
          base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
              FROM_HERE,
              base::BindOnce(std::move(result_callback), ResultType::kFailure,
                             CreatePayload(MakeInvalidParametersResponse())));
          break;
        }
        routine_duration = ash::cros_healthd::mojom::NullableUint32::New(value);
      }
      diagnostics_service->RunCpuStressRoutine(
          std::move(routine_duration),
          base::BindOnce(
              &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
              weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::
        kFloatingPointAccuracy: {
      constexpr char kLengthSecondsFieldName[] = "lengthSeconds";
      std::optional<int> length_seconds =
          params_dict_.FindInt(kLengthSecondsFieldName);
      ash::cros_healthd::mojom::NullableUint32Ptr routine_duration;
      if (length_seconds.has_value()) {
        // If the optional integer parameter is specified, it must be >= 0.
        int value = length_seconds.value();
        if (value < 0) {
          SYSLOG(ERROR)
              << "Invalid parameters for floating point accuracy routine.";
          base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
              FROM_HERE,
              base::BindOnce(std::move(result_callback), ResultType::kFailure,
                             CreatePayload(MakeInvalidParametersResponse())));
          break;
        }
        routine_duration = ash::cros_healthd::mojom::NullableUint32::New(value);
      }
      diagnostics_service->RunFloatingPointAccuracyRoutine(
          std::move(routine_duration),
          base::BindOnce(
              &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
              weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::
        DEPRECATED_kNvmeWearLevel: {
      NOTIMPLEMENTED();
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kNvmeSelfTest: {
      constexpr char kNvmeSelfTestTypeFieldName[] = "nvmeSelfTestType";
      std::optional<int> nvme_self_test_type =
          params_dict_.FindInt(kNvmeSelfTestTypeFieldName);
      ash::cros_healthd::mojom::NvmeSelfTestTypeEnum nvme_self_test_type_enum;
      // The NVMe self-test routine expects a valid NvmeSelfTestTypeEnum.
      if (!nvme_self_test_type.has_value() ||
          !PopulateMojoEnumValueIfValid(nvme_self_test_type.value(),
                                        &nvme_self_test_type_enum)) {
        SYSLOG(ERROR) << "Invalid parameters for NVMe self-test routine.";
        base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE,
            base::BindOnce(std::move(result_callback), ResultType::kFailure,
                           CreatePayload(MakeInvalidParametersResponse())));
        break;
      }
      diagnostics_service->RunNvmeSelfTestRoutine(
          nvme_self_test_type_enum,
          base::BindOnce(
              &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
              weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kDiskRead: {
      constexpr char kTypeFieldName[] = "type";
      constexpr char kLengthSecondsFieldName[] = "lengthSeconds";
      constexpr char kFileSizeMbFieldName[] = "fileSizeMb";
      std::optional<int> type = params_dict_.FindInt(kTypeFieldName);
      std::optional<int> length_seconds =
          params_dict_.FindInt(kLengthSecondsFieldName);
      std::optional<int> file_size_mb =
          params_dict_.FindInt(kFileSizeMbFieldName);
      ash::cros_healthd::mojom::DiskReadRoutineTypeEnum type_enum;
      if (!length_seconds.has_value() || length_seconds.value() < 0 ||
          !file_size_mb.has_value() || file_size_mb.value() < 0 ||
          !type.has_value() ||
          !PopulateMojoEnumValueIfValid(type.value(), &type_enum)) {
        SYSLOG(ERROR) << "Invalid parameters for disk read routine.";
        base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE,
            base::BindOnce(std::move(result_callback), ResultType::kFailure,
                           CreatePayload(MakeInvalidParametersResponse())));
        break;
      }
      diagnostics_service->RunDiskReadRoutine(
          type_enum, length_seconds.value(), file_size_mb.value(),
          base::BindOnce(
              &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
              weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kPrimeSearch: {
      constexpr char kLengthSecondsFieldName[] = "lengthSeconds";
      std::optional<int> length_seconds =
          params_dict_.FindInt(kLengthSecondsFieldName);
      ash::cros_healthd::mojom::NullableUint32Ptr routine_duration;
      if (length_seconds.has_value()) {
        // If the optional integer parameter is specified, it must be >= 0.
        int value = length_seconds.value();
        if (value < 0) {
          SYSLOG(ERROR) << "Invalid parameters for prime search routine.";
          base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
              FROM_HERE,
              base::BindOnce(std::move(result_callback), ResultType::kFailure,
                             CreatePayload(MakeInvalidParametersResponse())));
          break;
        }
        routine_duration = ash::cros_healthd::mojom::NullableUint32::New(value);
      }
      diagnostics_service->RunPrimeSearchRoutine(
          std::move(routine_duration),
          base::BindOnce(
              &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
              weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryDischarge: {
      constexpr char kLengthSecondsFieldName[] = "lengthSeconds";
      constexpr char kMaximumDischargePercentAllowedFieldName[] =
          "maximumDischargePercentAllowed";
      std::optional<int> length_seconds =
          params_dict_.FindInt(kLengthSecondsFieldName);
      std::optional<int> maximum_discharge_percent_allowed =
          params_dict_.FindInt(kMaximumDischargePercentAllowedFieldName);
      // The battery discharge routine expects two integers >= 0.
      if (!length_seconds.has_value() ||
          !maximum_discharge_percent_allowed.has_value() ||
          length_seconds.value() < 0 ||
          maximum_discharge_percent_allowed.value() < 0) {
        SYSLOG(ERROR) << "Invalid parameters for BatteryDischarge routine.";
        base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE,
            base::BindOnce(std::move(result_callback), ResultType::kFailure,
                           CreatePayload(MakeInvalidParametersResponse())));
        break;
      }
      diagnostics_service->RunBatteryDischargeRoutine(
          length_seconds.value(), maximum_discharge_percent_allowed.value(),
          base::BindOnce(
              &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
              weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryCharge: {
      constexpr char kLengthSecondsFieldName[] = "lengthSeconds";
      constexpr char kMinimumChargePercentRequiredFieldName[] =
          "minimumChargePercentRequired";
      std::optional<int> length_seconds =
          params_dict_.FindInt(kLengthSecondsFieldName);
      std::optional<int> minimum_charge_percent_required =
          params_dict_.FindInt(kMinimumChargePercentRequiredFieldName);
      // The battery charge routine expects two integers >= 0.
      if (!length_seconds.has_value() ||
          !minimum_charge_percent_required.has_value() ||
          length_seconds.value() < 0 ||
          minimum_charge_percent_required.value() < 0) {
        SYSLOG(ERROR) << "Invalid parameters for BatteryCharge routine.";
        base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE,
            base::BindOnce(std::move(result_callback), ResultType::kFailure,
                           CreatePayload(MakeInvalidParametersResponse())));
        break;
      }
      diagnostics_service->RunBatteryChargeRoutine(
          length_seconds.value(), minimum_charge_percent_required.value(),
          base::BindOnce(
              &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
              weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kMemory: {
      diagnostics_service->RunMemoryRoutine(
          std::nullopt,
          base::BindOnce(
              &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
              weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kLanConnectivity: {
      diagnostics_service->RunLanConnectivityRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kSignalStrength: {
      diagnostics_service->RunSignalStrengthRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kGatewayCanBePinged: {
      diagnostics_service->RunGatewayCanBePingedRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::
        kHasSecureWiFiConnection: {
      diagnostics_service->RunHasSecureWiFiConnectionRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kDnsResolverPresent: {
      diagnostics_service->RunDnsResolverPresentRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kDnsLatency: {
      diagnostics_service->RunDnsLatencyRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kDnsResolution: {
      diagnostics_service->RunDnsResolutionRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kCaptivePortal: {
      diagnostics_service->RunCaptivePortalRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kHttpFirewall: {
      diagnostics_service->RunHttpFirewallRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kHttpsFirewall: {
      diagnostics_service->RunHttpsFirewallRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kHttpsLatency: {
      diagnostics_service->RunHttpsLatencyRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kVideoConferencing: {
      std::string* stun_server_hostname =
          params_dict_.FindString(kStunServerHostnameFieldName);
      diagnostics_service->RunVideoConferencingRoutine(
          stun_server_hostname
              ? std::make_optional<std::string>(*stun_server_hostname)
              : std::nullopt,
          base::BindOnce(
              &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
              weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kArcHttp: {
      diagnostics_service->RunArcHttpRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kArcPing: {
      diagnostics_service->RunArcPingRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kArcDnsResolution: {
      diagnostics_service->RunArcDnsResolutionRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kSensitiveSensor: {
      diagnostics_service->RunSensitiveSensorRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kFingerprint: {
      diagnostics_service->RunFingerprintRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kFingerprintAlive: {
      diagnostics_service->RunFingerprintAliveRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kPrivacyScreen: {
      constexpr char kPrivacyScreenTargetState[] = "targetState";
      std::optional<bool> target_state =
          params_dict_.FindBool(kPrivacyScreenTargetState);
      diagnostics_service->RunPrivacyScreenRoutine(
          target_state.value_or(true),
          base::BindOnce(
              &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
              weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::
        DEPRECATED_kLedLitUp: {
      NOTIMPLEMENTED();
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kEmmcLifetime: {
      diagnostics_service->RunEmmcLifetimeRoutine(base::BindOnce(
          &DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::
        DEPRECATED_kAudioSetVolume: {
      NOTIMPLEMENTED();
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::
        DEPRECATED_kAudioSetGain: {
      NOTIMPLEMENTED();
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kBluetoothPower: {
      NOTIMPLEMENTED();
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kBluetoothDiscovery: {
      NOTIMPLEMENTED();
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kBluetoothScanning: {
      NOTIMPLEMENTED();
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kBluetoothPairing: {
      NOTIMPLEMENTED();
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kPowerButton: {
      NOTIMPLEMENTED();
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kAudioDriver: {
      NOTIMPLEMENTED();
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kUfsLifetime: {
      NOTIMPLEMENTED();
      break;
    }
    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kFan: {
      NOTIMPLEMENTED();
      break;
    }
  }
}

void DeviceCommandRunRoutineJob::OnCrosHealthdResponseReceived(
    CallbackWithResult result_callback,
    ash::cros_healthd::mojom::RunRoutineResponsePtr response) {
  if (!response) {
    SYSLOG(ERROR) << "No RunRoutineResponse received from cros_healthd.";
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(result_callback),
                                  ResultType::kFailure, std::nullopt));
    return;
  }

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(result_callback), ResultType::kSuccess,
                     CreatePayload(std::move(response))));
}

}  // namespace policy