chromium/chrome/browser/chromeos/extensions/telemetry/api/diagnostics/diagnostics_api_converters.cc

// Copyright 2021 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/chromeos/extensions/telemetry/api/diagnostics/diagnostics_api_converters.h"

#include <optional>

#include "base/notreached.h"
#include "base/time/time.h"
#include "chrome/common/chromeos/extensions/api/diagnostics.h"
#include "chromeos/crosapi/mojom/diagnostics_service.mojom.h"
#include "chromeos/crosapi/mojom/telemetry_diagnostic_routine_service.mojom.h"

namespace chromeos::converters::diagnostics {

namespace {

namespace cx_diag = ::chromeos::api::os_diagnostics;
namespace crosapi = ::crosapi::mojom;

// All fields of `cx_diag::CreateRoutineArgumentsUnion`. The enums are defined
// manually because there are no tools to generate them automatically.
enum class CreateRoutineArgumentsField {
  kMemory,
  kVolumeButton,
  kFan,
  kNetworkBandwidth,
  kLedLitUp,
  kCameraFrameAnalysis,
  kKeyboardBacklight,
};

// All fields of `cx_diag::RoutineInquiryReplyUnion`. The enums are defined
// manually because there are no tools to generate them automatically.
enum class RoutineInquiryReplyField {
  kCheckLedLitUpState,
  kCheckKeyboardBacklightState,
};

std::optional<crosapi::TelemetryDiagnosticRoutineArgumentPtr>
ConvertExtensionUnionToMojoUnion(
    const cx_diag::CreateMemoryRoutineArguments& cx_args) {
  if (cx_args.max_testing_mem_kib.has_value() &&
      cx_args.max_testing_mem_kib.value() < 0) {
    return std::nullopt;
  }

  auto args = crosapi::TelemetryDiagnosticMemoryRoutineArgument::New();
  args->max_testing_mem_kib = cx_args.max_testing_mem_kib;
  return crosapi::TelemetryDiagnosticRoutineArgument::NewMemory(
      std::move(args));
}

std::optional<crosapi::TelemetryDiagnosticRoutineArgumentPtr>
ConvertExtensionUnionToMojoUnion(
    const cx_diag::CreateVolumeButtonRoutineArguments& cx_args) {
  if (cx_args.timeout_seconds <= 0 ||
      cx_args.button_type == cx_diag::VolumeButtonType::kNone) {
    return std::nullopt;
  }

  auto args = crosapi::TelemetryDiagnosticVolumeButtonRoutineArgument::New();
  args->type = ConvertVolumeButtonRoutineButtonType(cx_args.button_type);
  args->timeout = base::Seconds(cx_args.timeout_seconds);
  return crosapi::TelemetryDiagnosticRoutineArgument::NewVolumeButton(
      std::move(args));
}

std::optional<crosapi::TelemetryDiagnosticRoutineArgumentPtr>
ConvertExtensionUnionToMojoUnion(
    const cx_diag::CreateFanRoutineArguments& cx_args) {
  return crosapi::TelemetryDiagnosticRoutineArgument::NewFan(
      crosapi::TelemetryDiagnosticFanRoutineArgument::New());
}

std::optional<crosapi::TelemetryDiagnosticRoutineArgumentPtr>
ConvertExtensionUnionToMojoUnion(
    const cx_diag::CreateNetworkBandwidthRoutineArguments& cx_args) {
  return crosapi::TelemetryDiagnosticRoutineArgument::NewNetworkBandwidth(
      crosapi::TelemetryDiagnosticNetworkBandwidthRoutineArgument::New());
}

std::optional<crosapi::TelemetryDiagnosticRoutineArgumentPtr>
ConvertExtensionUnionToMojoUnion(
    const cx_diag::CreateLedLitUpRoutineArguments& cx_args) {
  auto args = crosapi::TelemetryDiagnosticLedLitUpRoutineArgument::New();
  args->name = ConvertLedName(cx_args.name);
  args->color = ConvertLedColor(cx_args.color);
  return crosapi::TelemetryDiagnosticRoutineArgument::NewLedLitUp(
      std::move(args));
}

std::optional<crosapi::TelemetryDiagnosticRoutineArgumentPtr>
ConvertExtensionUnionToMojoUnion(
    const cx_diag::CreateCameraFrameAnalysisRoutineArguments& cx_args) {
  return crosapi::TelemetryDiagnosticRoutineArgument::NewCameraFrameAnalysis(
      crosapi::TelemetryDiagnosticCameraFrameAnalysisRoutineArgument::New());
}

std::optional<crosapi::TelemetryDiagnosticRoutineArgumentPtr>
ConvertExtensionUnionToMojoUnion(
    const cx_diag::CreateKeyboardBacklightRoutineArguments& cx_args) {
  return crosapi::TelemetryDiagnosticRoutineArgument::NewKeyboardBacklight(
      crosapi::TelemetryDiagnosticKeyboardBacklightRoutineArgument::New());
}

std::optional<crosapi::TelemetryDiagnosticRoutineInquiryReplyPtr>
ConvertExtensionUnionToMojoUnion(
    const cx_diag::CheckLedLitUpStateReply& cx_args) {
  auto args = crosapi::TelemetryDiagnosticCheckLedLitUpStateReply::New();
  args->state = ConvertLedLitUpState(cx_args.state);
  return crosapi::TelemetryDiagnosticRoutineInquiryReply::NewCheckLedLitUpState(
      std::move(args));
}

std::optional<crosapi::TelemetryDiagnosticRoutineInquiryReplyPtr>
ConvertExtensionUnionToMojoUnion(
    const cx_diag::CheckKeyboardBacklightStateReply& cx_args) {
  auto args =
      crosapi::TelemetryDiagnosticCheckKeyboardBacklightStateReply::New();
  args->state = ConvertKeyboardBacklightState(cx_args.state);
  return crosapi::TelemetryDiagnosticRoutineInquiryReply::
      NewCheckKeyboardBacklightState(std::move(args));
}

// Default implementation of `ConvertExtensionUnionToMojoUnion` raises compile
// error.
template <typename Arg, typename OutputT>
OutputT ConvertExtensionUnionToMojoUnion(const Arg& arg) {
  static_assert(
      false, "ConvertExtensionUnionToMojoUnion for specific type not defined.");
  NOTREACHED();
}

std::vector<RoutineInquiryReplyField> GetNonNullFields(
    const cx_diag::RoutineInquiryReplyUnion& extension_union) {
  std::vector<RoutineInquiryReplyField> result;
  if (extension_union.check_led_lit_up_state.has_value()) {
    result.push_back(RoutineInquiryReplyField::kCheckLedLitUpState);
  }
  if (extension_union.check_keyboard_backlight_state.has_value()) {
    result.push_back(RoutineInquiryReplyField::kCheckKeyboardBacklightState);
  }
  return result;
}

std::vector<CreateRoutineArgumentsField> GetNonNullFields(
    const cx_diag::CreateRoutineArgumentsUnion& extension_union) {
  std::vector<CreateRoutineArgumentsField> result;
  if (extension_union.memory.has_value()) {
    result.push_back(CreateRoutineArgumentsField::kMemory);
  }
  if (extension_union.volume_button.has_value()) {
    result.push_back(CreateRoutineArgumentsField::kVolumeButton);
  }
  if (extension_union.fan.has_value()) {
    result.push_back(CreateRoutineArgumentsField::kFan);
  }
  if (extension_union.network_bandwidth.has_value()) {
    result.push_back(CreateRoutineArgumentsField::kNetworkBandwidth);
  }
  if (extension_union.led_lit_up.has_value()) {
    result.push_back(CreateRoutineArgumentsField::kLedLitUp);
  }
  if (extension_union.camera_frame_analysis.has_value()) {
    result.push_back(CreateRoutineArgumentsField::kCameraFrameAnalysis);
  }
  if (extension_union.keyboard_backlight.has_value()) {
    result.push_back(CreateRoutineArgumentsField::kKeyboardBacklight);
  }
  return result;
}

}  // namespace

bool ConvertMojoRoutine(crosapi::DiagnosticsRoutineEnum in,
                        cx_diag::RoutineType* out) {
  DCHECK(out);
  switch (in) {
    case crosapi::DiagnosticsRoutineEnum::kAcPower:
      *out = cx_diag::RoutineType::kAcPower;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kBatteryCapacity:
      *out = cx_diag::RoutineType::kBatteryCapacity;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kBatteryCharge:
      *out = cx_diag::RoutineType::kBatteryCharge;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kBatteryDischarge:
      *out = cx_diag::RoutineType::kBatteryDischarge;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kBatteryHealth:
      *out = cx_diag::RoutineType::kBatteryHealth;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kCpuCache:
      *out = cx_diag::RoutineType::kCpuCache;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kFloatingPointAccuracy:
      *out = cx_diag::RoutineType::kCpuFloatingPointAccuracy;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kPrimeSearch:
      *out = cx_diag::RoutineType::kCpuPrimeSearch;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kCpuStress:
      *out = cx_diag::RoutineType::kCpuStress;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kDiskRead:
      *out = cx_diag::RoutineType::kDiskRead;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kDnsResolution:
      *out = cx_diag::RoutineType::kDnsResolution;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kDnsResolverPresent:
      *out = cx_diag::RoutineType::kDnsResolverPresent;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kLanConnectivity:
      *out = cx_diag::RoutineType::kLanConnectivity;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kMemory:
      *out = cx_diag::RoutineType::kMemory;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kSignalStrength:
      *out = cx_diag::RoutineType::kSignalStrength;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kGatewayCanBePinged:
      *out = cx_diag::RoutineType::kGatewayCanBePinged;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kSmartctlCheck:
      *out = cx_diag::RoutineType::kSmartctlCheck;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kSensitiveSensor:
      *out = cx_diag::RoutineType::kSensitiveSensor;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kNvmeSelfTest:
      *out = cx_diag::RoutineType::kNvmeSelfTest;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kFingerprintAlive:
      *out = cx_diag::RoutineType::kFingerprintAlive;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kSmartctlCheckWithPercentageUsed:
      *out = cx_diag::RoutineType::kSmartctlCheckWithPercentageUsed;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kEmmcLifetime:
      *out = cx_diag::RoutineType::kEmmcLifetime;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kBluetoothPower:
      *out = cx_diag::RoutineType::kBluetoothPower;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kUfsLifetime:
      *out = cx_diag::RoutineType::kUfsLifetime;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kPowerButton:
      *out = cx_diag::RoutineType::kPowerButton;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kAudioDriver:
      *out = cx_diag::RoutineType::kAudioDriver;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kBluetoothDiscovery:
      *out = cx_diag::RoutineType::kBluetoothDiscovery;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kBluetoothScanning:
      *out = cx_diag::RoutineType::kBluetoothScanning;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kBluetoothPairing:
      *out = cx_diag::RoutineType::kBluetoothPairing;
      return true;
    case crosapi::DiagnosticsRoutineEnum::kFan:
      *out = cx_diag::RoutineType::kFan;
      return true;
    // Below are deprecated routines.
    case crosapi::DiagnosticsRoutineEnum::DEPRECATED_kNvmeWearLevel:
    case crosapi::DiagnosticsRoutineEnum::kUnknown:
      return false;
  }
}

cx_diag::RoutineStatus ConvertRoutineStatus(
    crosapi::DiagnosticsRoutineStatusEnum status) {
  switch (status) {
    case crosapi::DiagnosticsRoutineStatusEnum::kUnknown:
      return cx_diag::RoutineStatus::kUnknown;
    case crosapi::DiagnosticsRoutineStatusEnum::kReady:
      return cx_diag::RoutineStatus::kReady;
    case crosapi::DiagnosticsRoutineStatusEnum::kRunning:
      return cx_diag::RoutineStatus::kRunning;
    case crosapi::DiagnosticsRoutineStatusEnum::kWaiting:
      return cx_diag::RoutineStatus::kWaitingUserAction;
    case crosapi::DiagnosticsRoutineStatusEnum::kPassed:
      return cx_diag::RoutineStatus::kPassed;
    case crosapi::DiagnosticsRoutineStatusEnum::kFailed:
      return cx_diag::RoutineStatus::kFailed;
    case crosapi::DiagnosticsRoutineStatusEnum::kError:
      return cx_diag::RoutineStatus::kError;
    case crosapi::DiagnosticsRoutineStatusEnum::kCancelled:
      return cx_diag::RoutineStatus::kCancelled;
    case crosapi::DiagnosticsRoutineStatusEnum::kFailedToStart:
      return cx_diag::RoutineStatus::kFailedToStart;
    case crosapi::DiagnosticsRoutineStatusEnum::kRemoved:
      return cx_diag::RoutineStatus::kRemoved;
    case crosapi::DiagnosticsRoutineStatusEnum::kCancelling:
      return cx_diag::RoutineStatus::kCancelling;
    case crosapi::DiagnosticsRoutineStatusEnum::kUnsupported:
      return cx_diag::RoutineStatus::kUnsupported;
    case crosapi::DiagnosticsRoutineStatusEnum::kNotRun:
      return cx_diag::RoutineStatus::kNotRun;
  }
}

crosapi::DiagnosticsRoutineCommandEnum ConvertRoutineCommand(
    cx_diag::RoutineCommandType commandType) {
  switch (commandType) {
    case cx_diag::RoutineCommandType::kCancel:
      return crosapi::DiagnosticsRoutineCommandEnum::kCancel;
    case cx_diag::RoutineCommandType::kRemove:
      return crosapi::DiagnosticsRoutineCommandEnum::kRemove;
    case cx_diag::RoutineCommandType::kResume:
      return crosapi::DiagnosticsRoutineCommandEnum::kContinue;
    case cx_diag::RoutineCommandType::kStatus:
      return crosapi::DiagnosticsRoutineCommandEnum::kGetStatus;
    case cx_diag::RoutineCommandType::kNone:
      break;
  }
  NOTREACHED();
}

crosapi::DiagnosticsAcPowerStatusEnum ConvertAcPowerStatusRoutineType(
    cx_diag::AcPowerStatus routineType) {
  switch (routineType) {
    case cx_diag::AcPowerStatus::kConnected:
      return crosapi::DiagnosticsAcPowerStatusEnum::kConnected;
    case cx_diag::AcPowerStatus::kDisconnected:
      return crosapi::DiagnosticsAcPowerStatusEnum::kDisconnected;
    case cx_diag::AcPowerStatus::kNone:
      break;
  }
  NOTREACHED();
}

cx_diag::UserMessageType ConvertRoutineUserMessage(
    crosapi::DiagnosticsRoutineUserMessageEnum userMessage) {
  switch (userMessage) {
    case crosapi::DiagnosticsRoutineUserMessageEnum::kUnknown:
      return cx_diag::UserMessageType::kUnknown;
    case crosapi::DiagnosticsRoutineUserMessageEnum::kUnplugACPower:
      return cx_diag::UserMessageType::kUnplugAcPower;
    case crosapi::DiagnosticsRoutineUserMessageEnum::kPlugInACPower:
      return cx_diag::UserMessageType::kPlugInAcPower;
    case crosapi::DiagnosticsRoutineUserMessageEnum::kPressPowerButton:
      return cx_diag::UserMessageType::kPressPowerButton;
  }
}

crosapi::DiagnosticsDiskReadRoutineTypeEnum ConvertDiskReadRoutineType(
    cx_diag::DiskReadRoutineType routineType) {
  switch (routineType) {
    case cx_diag::DiskReadRoutineType::kLinear:
      return crosapi::DiagnosticsDiskReadRoutineTypeEnum::kLinearRead;
    case cx_diag::DiskReadRoutineType::kRandom:
      return crosapi::DiagnosticsDiskReadRoutineTypeEnum::kRandomRead;
    case cx_diag::DiskReadRoutineType::kNone:
      break;
  }
  NOTREACHED();
}

crosapi::DiagnosticsNvmeSelfTestTypeEnum ConvertNvmeSelfTestRoutineType(
    cx_diag::RunNvmeSelfTestRequest routine_type) {
  switch (routine_type.test_type) {
    case cx_diag::NvmeSelfTestType::kNone:
      return crosapi::DiagnosticsNvmeSelfTestTypeEnum::kUnknown;
    case cx_diag::NvmeSelfTestType::kShortTest:
      return crosapi::DiagnosticsNvmeSelfTestTypeEnum::kShortSelfTest;
    case cx_diag::NvmeSelfTestType::kLongTest:
      return crosapi::DiagnosticsNvmeSelfTestTypeEnum::kLongSelfTest;
  }
}

crosapi::TelemetryDiagnosticVolumeButtonRoutineArgument::ButtonType
ConvertVolumeButtonRoutineButtonType(
    cx_diag::VolumeButtonType volume_button_type) {
  switch (volume_button_type) {
    case cx_diag::VolumeButtonType::kNone:
      return crosapi::TelemetryDiagnosticVolumeButtonRoutineArgument::
          ButtonType::kUnmappedEnumField;
    case cx_diag::VolumeButtonType::kVolumeUp:
      return crosapi::TelemetryDiagnosticVolumeButtonRoutineArgument::
          ButtonType::kVolumeUp;
    case cx_diag::VolumeButtonType::kVolumeDown:
      return crosapi::TelemetryDiagnosticVolumeButtonRoutineArgument::
          ButtonType::kVolumeDown;
  }
}

crosapi::TelemetryDiagnosticLedName ConvertLedName(cx_diag::LedName led_name) {
  switch (led_name) {
    case cx_diag::LedName::kNone:
      return crosapi::TelemetryDiagnosticLedName::kUnmappedEnumField;
    case cx_diag::LedName::kBattery:
      return crosapi::TelemetryDiagnosticLedName::kBattery;
    case cx_diag::LedName::kPower:
      return crosapi::TelemetryDiagnosticLedName::kPower;
    case cx_diag::LedName::kAdapter:
      return crosapi::TelemetryDiagnosticLedName::kAdapter;
    case cx_diag::LedName::kLeft:
      return crosapi::TelemetryDiagnosticLedName::kLeft;
    case cx_diag::LedName::kRight:
      return crosapi::TelemetryDiagnosticLedName::kRight;
  }
}

crosapi::TelemetryDiagnosticLedColor ConvertLedColor(
    cx_diag::LedColor led_color) {
  switch (led_color) {
    case cx_diag::LedColor::kNone:
      return crosapi::TelemetryDiagnosticLedColor::kUnmappedEnumField;
    case cx_diag::LedColor::kRed:
      return crosapi::TelemetryDiagnosticLedColor::kRed;
    case cx_diag::LedColor::kGreen:
      return crosapi::TelemetryDiagnosticLedColor::kGreen;
    case cx_diag::LedColor::kBlue:
      return crosapi::TelemetryDiagnosticLedColor::kBlue;
    case cx_diag::LedColor::kYellow:
      return crosapi::TelemetryDiagnosticLedColor::kYellow;
    case cx_diag::LedColor::kWhite:
      return crosapi::TelemetryDiagnosticLedColor::kWhite;
    case cx_diag::LedColor::kAmber:
      return crosapi::TelemetryDiagnosticLedColor::kAmber;
  }
}

crosapi::TelemetryDiagnosticCheckLedLitUpStateReply::State ConvertLedLitUpState(
    cx_diag::LedLitUpState led_lit_up_state) {
  switch (led_lit_up_state) {
    case cx_diag::LedLitUpState::kNone:
      return crosapi::TelemetryDiagnosticCheckLedLitUpStateReply::State::
          kUnmappedEnumField;
    case cx_diag::LedLitUpState::kCorrectColor:
      return crosapi::TelemetryDiagnosticCheckLedLitUpStateReply::State::
          kCorrectColor;
    case cx_diag::LedLitUpState::kNotLitUp:
      return crosapi::TelemetryDiagnosticCheckLedLitUpStateReply::State::
          kNotLitUp;
  }
}

crosapi::TelemetryDiagnosticCheckKeyboardBacklightStateReply::State
ConvertKeyboardBacklightState(
    cx_diag::KeyboardBacklightState keyboard_backlight_state) {
  switch (keyboard_backlight_state) {
    case cx_diag::KeyboardBacklightState::kNone:
      return crosapi::TelemetryDiagnosticCheckKeyboardBacklightStateReply::
          State::kUnmappedEnumField;
    case cx_diag::KeyboardBacklightState::kOk:
      return crosapi::TelemetryDiagnosticCheckKeyboardBacklightStateReply::
          State::kOk;
    case cx_diag::KeyboardBacklightState::kAnyNotLitUp:
      return crosapi::TelemetryDiagnosticCheckKeyboardBacklightStateReply::
          State::kAnyNotLitUp;
  }
}

std::optional<crosapi::TelemetryDiagnosticRoutineArgumentPtr>
ConvertRoutineArgumentsUnion(
    cx_diag::CreateRoutineArgumentsUnion extension_union) {
  std::vector<CreateRoutineArgumentsField> non_null_fields =
      GetNonNullFields(extension_union);

  if (non_null_fields.empty()) {
    // When extension is newer than the browser, extension might pass in a
    // routine argument that cannot be recognized by the browser. For better
    // developer experience, don't treat it as an invalid union.
    return crosapi::TelemetryDiagnosticRoutineArgument::NewUnrecognizedArgument(
        false);
  }

  // A dictionary-based union is invalid when more than one fields are set.
  if (non_null_fields.size() > 1) {
    return std::nullopt;
  }

  CHECK(non_null_fields.size() == 1);
  switch (non_null_fields.front()) {
    case CreateRoutineArgumentsField::kMemory:
      return ConvertExtensionUnionToMojoUnion(extension_union.memory.value());
    case CreateRoutineArgumentsField::kVolumeButton:
      return ConvertExtensionUnionToMojoUnion(
          extension_union.volume_button.value());
    case CreateRoutineArgumentsField::kFan:
      return ConvertExtensionUnionToMojoUnion(extension_union.fan.value());
    case CreateRoutineArgumentsField::kNetworkBandwidth:
      return ConvertExtensionUnionToMojoUnion(
          extension_union.network_bandwidth.value());
    case CreateRoutineArgumentsField::kLedLitUp:
      return ConvertExtensionUnionToMojoUnion(
          extension_union.led_lit_up.value());
    case CreateRoutineArgumentsField::kCameraFrameAnalysis:
      return ConvertExtensionUnionToMojoUnion(
          extension_union.camera_frame_analysis.value());
    case CreateRoutineArgumentsField::kKeyboardBacklight:
      return ConvertExtensionUnionToMojoUnion(
          extension_union.keyboard_backlight.value());
  }
}

std::optional<crosapi::TelemetryDiagnosticRoutineInquiryReplyPtr>
ConvertRoutineInquiryReplyUnion(
    cx_diag::RoutineInquiryReplyUnion extension_union) {
  std::vector<RoutineInquiryReplyField> non_null_fields =
      GetNonNullFields(extension_union);

  if (non_null_fields.empty()) {
    // When extension is newer than the browser, extension might pass in a reply
    // that cannot be recognized by the browser. For better developer
    // experience, don't treat it as an invalid union.
    return crosapi::TelemetryDiagnosticRoutineInquiryReply::
        NewUnrecognizedReply(false);
  }

  // A dictionary-based union is invalid when more than one fields are set.
  if (non_null_fields.size() > 1) {
    return std::nullopt;
  }

  CHECK(non_null_fields.size() == 1);
  switch (non_null_fields.front()) {
    case RoutineInquiryReplyField::kCheckLedLitUpState:
      return ConvertExtensionUnionToMojoUnion(
          extension_union.check_led_lit_up_state.value());
    case RoutineInquiryReplyField::kCheckKeyboardBacklightState:
      return ConvertExtensionUnionToMojoUnion(
          extension_union.check_keyboard_backlight_state.value());
  }
}

}  // namespace chromeos::converters::diagnostics