chromium/chrome/browser/ash/chromebox_for_meetings/xu_camera/xu_camera_service.cc

// Copyright 2022 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/chromebox_for_meetings/xu_camera/xu_camera_service.h"

#include <fcntl.h>
#include <libudev.h>
#include <linux/usb/video.h>
#include <linux/uvcvideo.h>
#include <linux/videodev2.h>
#include <stdlib.h>
#include <sys/ioctl.h>

#include <cstdint>
#include <utility>

#include "base/notreached.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/string_util.h"
#include "chrome/browser/media/webrtc/media_device_salt_service_factory.h"
#include "chromeos/ash/components/dbus/chromebox_for_meetings/cfm_hotline_client.h"
#include "components/media_device_salt/media_device_salt_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/device_service.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/media_device_id.h"
#include "content/public/browser/render_frame_host.h"
#include "services/device/public/mojom/usb_device.mojom.h"
#include "services/device/public/mojom/usb_enumeration_options.mojom.h"

using chromeos::IpPeripheralServiceClient;

namespace ash::cfm {

namespace {
static constexpr int kGuidSize = 16;
static constexpr int kSubtypeOffset = 2;
static constexpr int kVideoClass = 14;
static constexpr int kVideoSubclass = 1;
static constexpr int kXUSubtype = 6;

typedef struct {
  uint8_t kLength;
  uint8_t kType;
  uint8_t kSubtype;
  uint8_t kUnitId;
  uint8_t kGuidLe[kGuidSize];  // little-endian from camera
} kXuInterface;

static const char* kLocalIpAddress = "192.168.";  // Series One peripherals
static const std::initializer_list<uint8_t> kMeetXuGuidLe = {
    0x24, 0xE9, 0xD7, 0x74,  // bytes 0-3 (little-endian)
    0xC9, 0x49,              // bytes 4-5 (little-endian)
    0x45, 0x4A,              // bytes 6-7 (little-endian)
    0x98, 0xA3, 0x8A, 0x9F, 0x60, 0x06, 0x1E,
    0x83,  // bytes 8-15 (byte array)
};

class RealDelegate : public XuCameraService::Delegate {
 public:
  RealDelegate() = default;
  RealDelegate(const RealDelegate&) = delete;
  RealDelegate& operator=(const RealDelegate&) = delete;

  int Ioctl(const base::ScopedFD& fd,
            unsigned int request,
            void* query) override {
    VLOG(4) << __func__ << " with request: " << request;
    if (!fd.is_valid()) {
      LOG(ERROR) << "File Descriptor No longer valid";
      return EBADF;
    }
    return HANDLE_EINTR(ioctl(fd.get(), request, query));
  }

  bool OpenFile(base::ScopedFD& fd, const std::string& path) override {
    fd.reset(open(path.c_str(), O_RDWR | O_NONBLOCK, 0));
    VLOG(4) << __func__ << "File path: " << path;
    LOG_IF(ERROR, !fd.is_valid())
        << "Failed to open device. Open file failed. " << path;
    return fd.is_valid();
  }
};

bool IsIpCamera(const std::string& dev_path) {
  return base::StartsWith(dev_path, kLocalIpAddress);
}

IpPeripheralServiceClient::GetControlCallback ConvertGetCtrlCallbackForDbus(
    XuCameraService::GetCtrlCallback callback) {
  // Adapts a XuCameraService::GetCtrlCallback OnceCallback<void (uint8_t,
  // std::vector<unsigned char>)> to a
  // IpPeripheralServiceClient::GetControlCallback OnceCallback<void (bool,
  // std::vector<uint8_t>)> (Note difference in first argument.)
  return base::BindOnce(
      [](XuCameraService::GetCtrlCallback cb, bool success,
         std::vector<uint8_t> result) -> void {
        std::move(cb).Run(success ? 0 : EINVAL, std::move(result));
      },
      std::move(callback));
}

IpPeripheralServiceClient::SetControlCallback ConvertSetCtrlCallbackForDbus(
    XuCameraService::SetCtrlCallback callback) {
  // Adapts a XuCameraService::SetCtrlCallback OnceCallback<void (uint8_t)>
  // to a IpPeripheralServiceClient::SetControlCallback OnceCallback<void
  // (bool)> (Note difference in first argument.)
  return base::BindOnce(
      [](XuCameraService::SetCtrlCallback cb, bool success) -> void {
        std::move(cb).Run(success ? 0 : EINVAL);
      },
      std::move(callback));
}

void TranslateDeviceId(
    const std::string& hashed_device_id,
    base::OnceCallback<void(const std::optional<std::string>&)> callback,
    const url::Origin& security_origin,
    const std::string& salt) {
  auto translate_device_id_callback = base::BindOnce(
      [](const std::string& hashed_device_id,
         base::OnceCallback<void(const std::optional<std::string>&)> callback,
         const url::Origin& security_origin, const std::string& salt) {
        content::GetMediaDeviceIDForHMAC(
            blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, salt,
            security_origin, hashed_device_id,
            content::GetUIThreadTaskRunner({}), std::move(callback));
      },
      std::move(hashed_device_id), std::move(callback),
      std::move(security_origin), std::move(salt));

  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE, std::move(translate_device_id_callback));
}

XuCameraService* g_xu_camera_service = nullptr;

}  // namespace

XuCameraService::XuCameraService(Delegate* delegate)
    : delegate_(delegate),
      service_adaptor_(mojom::XuCamera::Name_, this),
      meet_xu_guid_le_(kMeetXuGuidLe) {
  CfmHotlineClient::Get()->AddObserver(this);
}

XuCameraService::~XuCameraService() {
  CfmHotlineClient::Get()->RemoveObserver(this);
}

// static
void XuCameraService::Initialize() {
  CHECK(!g_xu_camera_service);
  g_xu_camera_service = new XuCameraService(new RealDelegate());
}

// static
void XuCameraService::InitializeForTesting(Delegate* delegate) {
  CHECK(!g_xu_camera_service);
  g_xu_camera_service = new XuCameraService(delegate);
}

// static
void XuCameraService::Shutdown() {
  CHECK(g_xu_camera_service);
  delete g_xu_camera_service;
  g_xu_camera_service = nullptr;
}

// static
XuCameraService* XuCameraService::Get() {
  return g_xu_camera_service;
}

// static
bool XuCameraService::IsInitialized() {
  return g_xu_camera_service;
}

uint8_t XuCameraService::GetRequest(const mojom::GetFn& fn) {
  switch (fn) {
    case mojom::GetFn::kCur:
      return UVC_GET_CUR;
    case mojom::GetFn::kMin:
      return UVC_GET_MIN;
    case mojom::GetFn::kMax:
      return UVC_GET_MAX;
    case mojom::GetFn::kDef:
      return UVC_GET_DEF;
    case mojom::GetFn::kRes:
      return UVC_GET_RES;
    case mojom::GetFn::kLen:
      return UVC_GET_LEN;
    case mojom::GetFn::kInfo:
      return UVC_GET_INFO;
    default:
      LOG(ERROR) << __func__ << ": Invalid GetFn. ";
      return EINVAL;
  }
}

bool XuCameraService::ServiceRequestReceived(
    const std::string& interface_name) {
  if (interface_name != mojom::XuCamera::Name_) {
    return false;
  }
  service_adaptor_.BindServiceAdaptor();
  return true;
}

void XuCameraService::BindServiceContext(
    mojo::PendingReceiver<mojom::XuCamera> receiver,
    const content::GlobalRenderFrameHostId& id) {
  receivers_.Add(this, std::move(receiver), std::move(id));
}

void XuCameraService::OnBindService(
    mojo::ScopedMessagePipeHandle receiver_pipe) {
  // The Render Frame Host Id is used to identify the peripheral's device path
  // given a hased device id. If the origin of the client is not from within a
  // chromium renderer then the device paths would not be hashed.
  // We give a default RFH ID here for these cases that will fail if mistakenly
  // passed a HMAC ID.
  BindServiceContext(
      mojo::PendingReceiver<mojom::XuCamera>(std::move(receiver_pipe)),
      content::GlobalRenderFrameHostId());
}

void XuCameraService::OnAdaptorDisconnect() {
  receivers_.Clear();
}

void XuCameraService::SetDelegate(Delegate* delegate) {
  delegate_ = delegate;
}

void XuCameraService::GetUnitId(mojom::WebcamIdPtr id,
                                const std::vector<uint8_t>& guid_le,
                                GetUnitIdCallback callback) {
  auto host_id = receivers_.current_context();

  auto get_unit_id_callback = base::BindOnce(
      &XuCameraService::GetUnitIdWithDevicePath, weak_factory_.GetWeakPtr(),
      std::move(guid_le), std::move(callback));

  GetDevicePath(std::move(id), std::move(host_id),
                std::move(get_unit_id_callback));
}

void XuCameraService::GetUnitIdWithDevicePath(
    const std::vector<uint8_t>& guid_le,
    GetUnitIdCallback callback,
    const std::optional<std::string>& dev_path) {
  if (dev_path.has_value()) {
    const bool is_ip_camera = IsIpCamera(*dev_path);
    if (is_ip_camera) {
      VLOG(4) << __func__ << ": No UnitId for IP cameras";
      std::move(callback).Run(0, 0);
      return;
    }
    // TODO(b/260593636): Leverage WebRTC and GetDevicePath() once implemented
    auto unitId = guid_unitid_map_.find(guid_le);
    if (unitId != guid_unitid_map_.end()) {
      VLOG(4) << __func__ << ": UnitId found: "
              << static_cast<unsigned int>(unitId->second);
      std::move(callback).Run(0, unitId->second);
      return;
    }
  }

  if(!usb_manager_) {
    content::GetDeviceService().BindUsbDeviceManager(
        usb_manager_.BindNewPipeAndPassReceiver());
  }
  device::mojom::UsbEnumerationOptionsPtr options =
      device::mojom::UsbEnumerationOptions::New();
  usb_manager_->GetDevices(
      std::move(options),
      base::BindOnce(&XuCameraService::OnGetDevices, weak_factory_.GetWeakPtr(),
                     guid_le, std::move(callback)));
}

void XuCameraService::OnGetDevices(
    const std::vector<uint8_t>& guid_le,
    GetUnitIdCallback callback,
    std::vector<device::mojom::UsbDeviceInfoPtr> devices) {
  for (const auto& device_info : devices) {
    for (const auto& config : device_info->configurations) {
      for (const auto& interface : config->interfaces) {
        for (const auto& alternate : interface->alternates) {
          if (alternate->class_code == kVideoClass &&
              alternate->subclass_code == kVideoSubclass) {
            /* extra_data.data() is additional raw data in byte form
             * consisting of an array of interfaces. */
            uint8_t* data_ptr = alternate->extra_data.data();
            int end = alternate->extra_data.size();
            int cur = 0;
            kXuInterface curXuInterface;
            while ((cur + kSubtypeOffset) < end) {
              if (static_cast<int>(data_ptr[cur + kSubtypeOffset]) ==
                      kXUSubtype &&
                  (cur + (int)sizeof(curXuInterface)) < end) {
                std::memcpy(&curXuInterface, &data_ptr[cur],
                            sizeof(curXuInterface));
                std::vector<uint8_t> curXuInterface_guid_le(
                    curXuInterface.kGuidLe, curXuInterface.kGuidLe + kGuidSize);
                guid_unitid_map_.insert(
                    {curXuInterface_guid_le, curXuInterface.kUnitId});
              }
              cur += static_cast<int>(data_ptr[cur]);
            }
          }
        }
      }
    }
  }

  auto unitId = guid_unitid_map_.find(guid_le);
  if (unitId != guid_unitid_map_.end()) {
    VLOG(4) << __func__
            << ": UnitId found: " << static_cast<unsigned int>(unitId->second);
    std::move(callback).Run(0, unitId->second);
    return;
  }

  VLOG(4) << __func__ << ": UnitId not found";
  std::move(callback).Run(ENOSYS, '0');
}

void XuCameraService::MapCtrl(mojom::WebcamIdPtr id,
                              mojom::ControlMappingPtr mapping_ctrl,
                              MapCtrlCallback callback) {
  auto host_id = receivers_.current_context();

  auto map_ctrl_callback = base::BindOnce(
      &XuCameraService::MapCtrlWithDevicePath, weak_factory_.GetWeakPtr(),
      std::move(mapping_ctrl), std::move(callback));

  GetDevicePath(std::move(id), std::move(host_id),
                std::move(map_ctrl_callback));
}

void XuCameraService::MapCtrlWithDevicePath(
    const mojom::ControlMappingPtr mapping_ctrl,
    MapCtrlCallback callback,
    const std::optional<std::string>& dev_path) const {
  uint8_t error_code = 0;
  base::ScopedFD file_descriptor;

  if (!dev_path) {
    LOG(ERROR) << __func__ << ": Unable to determine device path";
    std::move(callback).Run(ENOENT);
    return;
  }

  VLOG(4) << __func__ << ": dev_path - " << *dev_path;

  if (!delegate_->OpenFile(file_descriptor, *dev_path)) {
    LOG(ERROR) << __func__ << ": File is invalid";
    std::move(callback).Run(ENOENT);
    return;
  }

  struct uvc_menu_info uvc_menus[mapping_ctrl->menu_entries->menu_info.size()];

  int index = 0;
  for (auto menu_info = mapping_ctrl->menu_entries->menu_info.begin();
       menu_info < mapping_ctrl->menu_entries->menu_info.end(); menu_info++) {
    const struct uvc_menu_info info = {
        .value = (*menu_info)->value,
        .name = {*((*menu_info)->name.data())},
    };
    uvc_menus[index] = info;
    index++;
  }

  struct uvc_xu_control_mapping control_mapping = {
      .id = mapping_ctrl->id,
      .name = {*(mapping_ctrl->name.data())},
      .entity = {*(mapping_ctrl->guid.data())},
      .selector = mapping_ctrl->selector,
      .size = mapping_ctrl->size,
      .offset = mapping_ctrl->offset,
      .v4l2_type = mapping_ctrl->v4l2_type,
      .data_type = mapping_ctrl->data_type,
      .menu_info = uvc_menus,
      .menu_count = static_cast<uint32_t>(index),
  };

  // Map the controls to v4l2
  error_code =
      delegate_->Ioctl(file_descriptor, UVCIOC_CTRL_MAP, &control_mapping);

  std::move(callback).Run(error_code);
}

void XuCameraService::GetCtrl(mojom::WebcamIdPtr id,
                              mojom::CtrlTypePtr ctrl,
                              mojom::GetFn fn,
                              GetCtrlCallback callback) {
  auto host_id = receivers_.current_context();

  auto get_ctrl_callback = base::BindOnce(
      &XuCameraService::GetCtrlWithDevicePath, weak_factory_.GetWeakPtr(),
      std::move(ctrl), std::move(fn), std::move(callback));

  GetDevicePath(std::move(id), std::move(host_id),
                std::move(get_ctrl_callback));
}

void XuCameraService::GetCtrlWithDevicePath(
    const mojom::CtrlTypePtr ctrl,
    const mojom::GetFn fn,
    GetCtrlCallback callback,
    const std::optional<std::string>& dev_path) const {
  std::vector<uint8_t> data;
  if (!dev_path) {
    LOG(ERROR) << __func__ << ": Unable to determine device path";
    std::move(callback).Run(ENOENT, data);
    return;
  }

  VLOG(4) << __func__ << ": dev_path - " << *dev_path;

  const bool is_ip_camera = IsIpCamera(*dev_path);
  base::ScopedFD file_descriptor;
  if (!is_ip_camera && !delegate_->OpenFile(file_descriptor, *dev_path)) {
    LOG(ERROR) << __func__ << ": File is invalid";
    std::move(callback).Run(ENOENT, data);
    return;
  }

  uint8_t error_code = 0;
  // GetCtrl depending on whether id provided is WebRTC or filepath
  switch (ctrl->which()) {
    case mojom::CtrlType::Tag::kQueryCtrl:
      if (is_ip_camera) {
        GetCtrlDbus(*dev_path, std::move(ctrl->get_query_ctrl()),
                    GetRequest(fn), std::move(callback));
        return;
      }
      error_code =
          CtrlThroughQuery(file_descriptor, std::move(ctrl->get_query_ctrl()),
                           data, GetRequest(fn));
      break;
    case mojom::CtrlType::Tag::kMappingCtrl:
      error_code = CtrlThroughMapping(
          file_descriptor, std::move(ctrl->get_mapping_ctrl()), data, fn);
      break;
    default:
      LOG(ERROR) << __func__ << ": Invalid CtrlType::Tag";
      error_code = EINVAL;
  }
  std::move(callback).Run(error_code, data);
}

void XuCameraService::SetCtrl(mojom::WebcamIdPtr id,
                              mojom::CtrlTypePtr ctrl,
                              const std::vector<uint8_t>& data,
                              SetCtrlCallback callback) {
  auto host_id = receivers_.current_context();

  auto set_ctrl_callback = base::BindOnce(
      &XuCameraService::SetCtrlWithDevicePath, weak_factory_.GetWeakPtr(),
      std::move(ctrl), std::move(data), std::move(callback));

  GetDevicePath(std::move(id), std::move(host_id),
                std::move(set_ctrl_callback));
}

void XuCameraService::SetCtrlWithDevicePath(
    const mojom::CtrlTypePtr ctrl,
    const std::vector<uint8_t>& data,
    SetCtrlCallback callback,
    const std::optional<std::string>& dev_path) const {
  if (!dev_path) {
    LOG(ERROR) << __func__ << ": Unable to determine device path";
    std::move(callback).Run(ENOENT);
    return;
  }

  VLOG(4) << __func__ << ": dev_path - " << *dev_path;

  const bool is_ip_camera = IsIpCamera(*dev_path);
  base::ScopedFD file_descriptor;
  if (!is_ip_camera && !delegate_->OpenFile(file_descriptor, *dev_path)) {
    LOG(ERROR) << __func__ << ": File is invalid";
    std::move(callback).Run(ENOENT);
    return;
  }

  uint8_t error_code = 0;
  std::vector<uint8_t> data_(data);
  // SetCtrl depending on whether id provided is WebRTC or filepath
  switch (ctrl->which()) {
    case mojom::CtrlType::Tag::kQueryCtrl:
      if (is_ip_camera) {
        SetCtrlDbus(*dev_path, std::move(ctrl->get_query_ctrl()), data_,
                    std::move(callback));
        return;
      }
      error_code =
          CtrlThroughQuery(file_descriptor, std::move(ctrl->get_query_ctrl()),
                           data_, UVC_SET_CUR);
      break;
    case mojom::CtrlType::Tag::kMappingCtrl: {
      mojom::ControlMappingPtr mapping = std::move(ctrl->get_mapping_ctrl());
      int32_t newValue;
      CopyFromData(&newValue, data_);
      struct v4l2_control control = {.id = mapping->id, .value = newValue};
      error_code = delegate_->Ioctl(file_descriptor, VIDIOC_S_CTRL, &control);
      break;
    }
    default:
      LOG(ERROR) << __func__ << ": Invalid CtrlType::Tag";
      error_code = EINVAL;
  }

  std::move(callback).Run(error_code);
}

uint8_t XuCameraService::QueryXuControl(const base::ScopedFD& file_descriptor,
                                        uint8_t unit_id,
                                        uint8_t selector,
                                        uint8_t* data,
                                        uint8_t query_request,
                                        uint16_t size) const {
  struct uvc_xu_control_query control_query;
  control_query.unit = unit_id;
  control_query.selector = selector;
  control_query.query = query_request;
  control_query.size = size;
  control_query.data = data;

  VLOG(4) << __func__ << ": unit_id -" << static_cast<unsigned int>(unit_id)
          << " selector - " << static_cast<unsigned int>(selector);
  int error =
      delegate_->Ioctl(file_descriptor, UVCIOC_CTRL_QUERY, &control_query);

  if (error < 0) {
    logging::SystemErrorCode err = logging::GetLastSystemErrorCode();
    LOG(ERROR) << "ioctl call failed. error: "
               << logging::SystemErrorCodeToString(err);
    return err;
  }
  return error;
}

void XuCameraService::GetDevicePath(
    mojom::WebcamIdPtr id,
    const content::GlobalRenderFrameHostId& host_id,
    base::OnceCallback<void(const std::optional<std::string>&)> callback)
    const {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (id->is_dev_path()) {
    std::move(callback).Run(id->get_dev_path());
    return;
  }

  // TODO(b/295912291): Check get_device_id is in a map
  auto hashed_device_id = id->get_device_id();

  if (!host_id || hashed_device_id.empty()) {
    std::move(callback).Run(std::nullopt);
    return;
  }

  content::RenderFrameHost* frame_host =
      content::RenderFrameHost::FromID(host_id);

  if (frame_host == nullptr) {
    VLOG(4) << __func__ << " frame_host == nullptr";
    std::move(callback).Run(std::nullopt);
    return;
  }

  content::BrowserContext* browser_context = frame_host->GetBrowserContext();

  if (browser_context == nullptr) {
    VLOG(4) << __func__ << " browser_context == nullptr";
    std::move(callback).Run(std::nullopt);
    return;
  }

  url::Origin security_origin = frame_host->GetLastCommittedOrigin();

  if (media_device_salt::MediaDeviceSaltService* salt_service =
          MediaDeviceSaltServiceFactory::GetInstance()->GetForBrowserContext(
              browser_context)) {
    salt_service->GetSalt(
        frame_host->GetStorageKey(),
        base::BindOnce(&TranslateDeviceId, hashed_device_id,
                       std::move(callback), std::move(security_origin)));
  } else {
    // If the embedder does not provide a salt service, use the browser
    // context's unique ID as salt.
    TranslateDeviceId(hashed_device_id, std::move(callback),
                      std::move(security_origin), browser_context->UniqueId());
  }
}

void XuCameraService::GetCtrlDbus(const std::string& dev_path,
                                  const mojom::ControlQueryPtr& query,
                                  const uint8_t& request,
                                  GetCtrlCallback callback) const {
  const std::string& ip_address = dev_path;
  VLOG(4) << __func__ << " ip - " << ip_address  //
          << " selector - " << static_cast<unsigned int>(query->selector)
          << " request - " << static_cast<unsigned int>(request);

  auto* ip_peripheral_service_client = IpPeripheralServiceClient::Get();
  auto get_control_callback =
      ConvertGetCtrlCallbackForDbus(std::move(callback));
  if (ip_peripheral_service_client) {
    ip_peripheral_service_client->GetControl(ip_address, meet_xu_guid_le_,
                                             query->selector, request,
                                             std::move(get_control_callback));
  } else {
    LOG(ERROR) << __func__ << " failed to get IpPeripheralServiceClient";
    std::move(get_control_callback).Run(false, std::vector<uint8_t>());
  }
}

void XuCameraService::SetCtrlDbus(const std::string& dev_path,
                                  const mojom::ControlQueryPtr& query,
                                  const std::vector<uint8_t>& data,
                                  SetCtrlCallback callback) const {
  const std::string& ip_address = dev_path;
  VLOG(4) << __func__ << " ip - " << ip_address  //
          << " selector - " << static_cast<unsigned int>(query->selector);

  auto* ip_peripheral_service_client = IpPeripheralServiceClient::Get();
  auto set_control_callback =
      ConvertSetCtrlCallbackForDbus(std::move(callback));
  if (ip_peripheral_service_client) {
    ip_peripheral_service_client->SetControl(ip_address, meet_xu_guid_le_,
                                             query->selector, data,
                                             std::move(set_control_callback));
  } else {
    LOG(ERROR) << __func__ << " failed to get IpPeripheralServiceClient";
    std::move(set_control_callback).Run(false);
  }
}

uint8_t XuCameraService::CtrlThroughQuery(const base::ScopedFD& file_descriptor,
                                          const mojom::ControlQueryPtr& query,
                                          std::vector<uint8_t>& data,
                                          const uint8_t& request) const {
  VLOG(4) << __func__ << " request - " << static_cast<unsigned int>(request)
          << " selector - " << static_cast<unsigned int>(query->selector);
  uint8_t data_len;
  uint8_t error_code = 0;
  if (UVC_SET_CUR == request) {
    error_code =
        QueryXuControl(file_descriptor, query->unit_id, query->selector,
                       data.data(), request, data.size());
    return error_code;
  } else if (UVC_GET_INFO == request) {
    data_len = 1;
  } else {
    data.clear();
    data.resize(2);
    error_code = GetLength(data.data(), file_descriptor, query->unit_id,
                           query->selector);
    if (error_code != 0 || UVC_GET_LEN == request) {
      return error_code;
    }
    // Use the queried data as data length for GetCtrl.
    // UVC_GET_LEN return values is always returned as little-endian 16-bit
    // integer by the device.
    data_len = le16toh(data[0] | (data[1] << 8));
  }

  data.clear();
  data.resize(data_len);

  error_code = QueryXuControl(file_descriptor, query->unit_id, query->selector,
                              data.data(), request, data_len);
  VLOG(4) << __func__
          << "query data error_code: " << static_cast<unsigned int>(error_code);

  return error_code;
}

uint8_t XuCameraService::CtrlThroughMapping(
    const base::ScopedFD& file_descriptor,
    const mojom::ControlMappingPtr& mapping,
    std::vector<uint8_t>& data,
    const mojom::GetFn& fn) const {
  uint8_t error_code = 0;

  VLOG(4) << __func__ << " GetFn - " << fn;
  // Early return for kCur/kLen  vs other info that requires VIDIOC_QUERYCTRL
  if (mojom::GetFn::kLen == fn) {
    // User set up the map so they should know that the size returned will be
    // in bits.
    data.push_back(mapping->size);
    return error_code;
  } else if (mojom::GetFn::kCur == fn) {
    struct v4l2_control control = {.id = mapping->id};
    error_code = delegate_->Ioctl(file_descriptor, VIDIOC_G_CTRL, &control);
    CopyToData<int32_t>(&control.value, data, sizeof(control.value));
    return error_code;
  }

  struct v4l2_queryctrl query = {
      .id = mapping->id,
  };

  error_code = delegate_->Ioctl(file_descriptor, VIDIOC_QUERYCTRL, &query);

  if (error_code != 0) {
    LOG(ERROR) << __func__ << " VIDIOC_QUERYCTRL error_code - " << error_code;
    return error_code;
  }

  switch (fn) {
    case mojom::GetFn::kMin: {
      CopyToData<int32_t>(&query.minimum, data, sizeof(query.minimum));
      break;
    }
    case mojom::GetFn::kMax: {
      CopyToData<int32_t>(&query.maximum, data, sizeof(query.maximum));
      break;
    }
    case mojom::GetFn::kDef: {
      CopyToData<int32_t>(&query.default_value, data,
                          sizeof(query.default_value));
      break;
    }
    case mojom::GetFn::kRes: {
      CopyToData<int32_t>(&query.step, data, sizeof(query.step));
      break;
    }
    case mojom::GetFn::kInfo: {
      // Get control info bitmap for get/set
      CopyToData<uint32_t>(&query.flags, data, sizeof(query.flags));
      break;
    }
    default:
      LOG(ERROR) << __func__ << ": Invalid GetFn. ";
      return EINVAL;
  }
  VLOG(4) << __func__ << ": Success query";
  return error_code;
}

template <typename T>
void XuCameraService::CopyToData(T* value,
                                 std::vector<uint8_t>& data,
                                 size_t size) const {
  VLOG(4) << __func__ << " of size " << size;
  data.reserve(size);
  uint8_t* valueAsUint8 = reinterpret_cast<uint8_t*>(value);
  for (size_t i = 0; i < size; ++i) {
    data.push_back(*valueAsUint8);
    valueAsUint8++;
  }
}

template <typename T>
void XuCameraService::CopyFromData(T* value,
                                   const std::vector<uint8_t>& data) const {
  int shiftBit = 0;
  for (size_t i = 0; i < data.size(); ++i) {
    *value += data[i] << shiftBit;
    shiftBit += 8;
  }
}

uint8_t XuCameraService::GetLength(uint8_t* data,
                                   const base::ScopedFD& file_descriptor,
                                   const uint8_t& unit_id,
                                   const uint8_t& selector) const {
  // UVC_GET_LEN is always size of 2
  uint8_t error_code =
      QueryXuControl(file_descriptor, unit_id, selector, data, UVC_GET_LEN, 2);
  VLOG_IF(4, (error_code != 0))
      << __func__
      << "query length error_code: " << static_cast<unsigned int>(error_code);
  return error_code;
}

}  // namespace ash::cfm