chromium/chromecast/device/bluetooth/le/le_scan_result.cc

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

#include "chromecast/device/bluetooth/le/le_scan_result.h"

#include <algorithm>

#include "base/logging.h"
#include "chromecast/device/bluetooth/bluetooth_util.h"

namespace chromecast {
namespace bluetooth {

namespace {

template <typename T, bluetooth_v2_shlib::Uuid (*converter)(T)>
std::optional<LeScanResult::UuidList> GetUuidsFromShort(
    const std::map<uint8_t, std::vector<std::vector<uint8_t>>>& type_to_data,
    uint8_t type) {
  auto it = type_to_data.find(type);
  if (it == type_to_data.end()) {
    return std::nullopt;
  }

  LeScanResult::UuidList ret;
  for (const auto& field : it->second) {
    if (field.size() % sizeof(T)) {
      LOG(ERROR) << "Invalid length, expected multiple of " << sizeof(T);
      return std::nullopt;
    }

    for (size_t i = 0; i < field.size(); i += sizeof(T)) {
      // Uuids are transmitted in little endian byte order. (Bluetooth Core
      // Specification v4.0 Vol 3 Part C Section 11.1).
      T value = 0;
      for (size_t j = 0; j < sizeof(T); ++j) {
        value |= field[i + j] << 8 * j;
      }

      ret.push_back(converter(value));
    }
  }

  return ret;
}

std::optional<LeScanResult::UuidList> GetUuidsAsUuid(
    const std::map<uint8_t, std::vector<std::vector<uint8_t>>>& type_to_data,
    uint8_t type) {
  auto it = type_to_data.find(type);
  if (it == type_to_data.end()) {
    return std::nullopt;
  }

  LeScanResult::UuidList ret;
  for (const auto& field : it->second) {
    if (field.size() % sizeof(bluetooth_v2_shlib::Uuid)) {
      LOG(ERROR) << "Invalid length, expected multiple of "
                 << sizeof(bluetooth_v2_shlib::Uuid);
      return std::nullopt;
    }

    for (size_t i = 0; i < field.size();
         i += sizeof(bluetooth_v2_shlib::Uuid)) {
      ret.emplace_back();
      // GAP UUIDs are little endian and bluetooth_v2_shlib::Uuid is big endian.
      std::reverse_copy(field.begin() + i,
                        field.begin() + i + sizeof(bluetooth_v2_shlib::Uuid),
                        ret.back().begin());
    }
  }

  return ret;
}

}  // namespace

LeScanResult::LeScanResult() = default;
LeScanResult::LeScanResult(const LeScanResult& other) = default;
LeScanResult::~LeScanResult() = default;

bool LeScanResult::SetAdvData(base::span<const uint8_t> advertisement_data) {
  std::map<uint8_t, std::vector<std::vector<uint8_t>>> new_type_to_data;

  size_t i = 0;
  while (i < advertisement_data.size()) {
    if (i + 1 == advertisement_data.size()) {
      LOG(ERROR) << "Malformed BLE packet";
      return false;
    }

    // http://www.argenox.com/bluetooth-low-energy-ble-v4-0-development/library/a-ble-advertising-primer/
    // Format:
    // [size][type][payload     ]
    // [i   ][i+1 ][i+2:i+1+size]
    //
    // Note: size does not include its own byte
    uint8_t size = advertisement_data[i];
    uint8_t type = advertisement_data[i + 1];

    // Avoid infinite loop if invalid data
    if (size == 0 || i + 1 + size > advertisement_data.size()) {
      LOG(ERROR) << "Invalid size";
      return false;
    }

    base::span<const uint8_t> data =
        advertisement_data.subspan(i + 2, size - 1);
    new_type_to_data[type].emplace_back(data.begin(), data.end());

    i += (size + 1);
  }

  adv_data.assign(advertisement_data.begin(), advertisement_data.end());
  type_to_data.swap(new_type_to_data);
  return true;
}

std::optional<std::string> LeScanResult::Name() const {
  auto it = type_to_data.find(kGapCompleteName);
  if (it != type_to_data.end()) {
    DCHECK_GE(it->second.size(), 1u);
    return std::string(reinterpret_cast<const char*>(it->second[0].data()),
                       it->second[0].size());
  }

  it = type_to_data.find(kGapShortName);
  if (it != type_to_data.end()) {
    DCHECK_GE(it->second.size(), 1u);
    return std::string(reinterpret_cast<const char*>(it->second[0].data()),
                       it->second[0].size());
  }

  return std::nullopt;
}

std::optional<uint8_t> LeScanResult::Flags() const {
  auto it = type_to_data.find(kGapFlags);
  if (it == type_to_data.end()) {
    return std::nullopt;
  }

  DCHECK_GE(it->second.size(), 1u);
  if (it->second[0].size() != 1) {
    LOG(ERROR) << "Invalid length for flags";
    return std::nullopt;
  }

  return it->second[0][0];
}

std::optional<LeScanResult::UuidList> LeScanResult::AllServiceUuids() const {
  bool any_exist = false;
  UuidList ret;
  auto insert_if_exists = [&ret, &any_exist](std::optional<UuidList> list) {
    if (list) {
      any_exist = true;
      ret.insert(ret.end(), list->begin(), list->end());
    }
  };

  insert_if_exists(IncompleteListOf16BitServiceUuids());
  insert_if_exists(CompleteListOf16BitServiceUuids());
  insert_if_exists(IncompleteListOf32BitServiceUuids());
  insert_if_exists(CompleteListOf32BitServiceUuids());
  insert_if_exists(IncompleteListOf128BitServiceUuids());
  insert_if_exists(CompleteListOf128BitServiceUuids());

  if (!any_exist) {
    return std::nullopt;
  }

  return ret;
}

std::optional<LeScanResult::UuidList>
LeScanResult::IncompleteListOf16BitServiceUuids() const {
  return GetUuidsFromShort<uint16_t, util::UuidFromInt16>(
      type_to_data, kGapIncomplete16BitServiceUuids);
}

std::optional<LeScanResult::UuidList>
LeScanResult::CompleteListOf16BitServiceUuids() const {
  return GetUuidsFromShort<uint16_t, util::UuidFromInt16>(
      type_to_data, kGapComplete16BitServiceUuids);
}

std::optional<LeScanResult::UuidList>
LeScanResult::IncompleteListOf32BitServiceUuids() const {
  return GetUuidsFromShort<uint32_t, util::UuidFromInt32>(
      type_to_data, kGapIncomplete32BitServiceUuids);
}

std::optional<LeScanResult::UuidList>
LeScanResult::CompleteListOf32BitServiceUuids() const {
  return GetUuidsFromShort<uint32_t, util::UuidFromInt32>(
      type_to_data, kGapComplete32BitServiceUuids);
}

std::optional<LeScanResult::UuidList>
LeScanResult::IncompleteListOf128BitServiceUuids() const {
  return GetUuidsAsUuid(type_to_data, kGapIncomplete128BitServiceUuids);
}

std::optional<LeScanResult::UuidList>
LeScanResult::CompleteListOf128BitServiceUuids() const {
  return GetUuidsAsUuid(type_to_data, kGapComplete128BitServiceUuids);
}

LeScanResult::ServiceDataMap LeScanResult::AllServiceData() const {
  ServiceDataMap ret;

  auto sd16 = ServiceData16Bit();
  ret.insert(sd16.begin(), sd16.end());

  auto sd32 = ServiceData32Bit();
  ret.insert(sd32.begin(), sd32.end());

  auto sd128 = ServiceData128Bit();
  ret.insert(sd128.begin(), sd128.end());

  return ret;
}

LeScanResult::ServiceDataMap LeScanResult::ServiceData16Bit() const {
  ServiceDataMap ret;
  auto it = type_to_data.find(kGapServicesData16bit);
  if (it == type_to_data.end()) {
    return ret;
  }

  for (const auto& data : it->second) {
    uint16_t uuid = 0;
    if (data.size() < sizeof(uuid)) {
      LOG(ERROR) << "Invalid service data, too short";
      ret.clear();
      return ret;
    }
    uuid = data[1] << 8 | data[0];
    ret[util::UuidFromInt16(uuid)] =
        std::vector<uint8_t>(data.begin() + sizeof(uuid), data.end());
  }

  return ret;
}

LeScanResult::ServiceDataMap LeScanResult::ServiceData32Bit() const {
  ServiceDataMap ret;
  auto it = type_to_data.find(kGapServicesData32bit);
  if (it == type_to_data.end()) {
    return ret;
  }

  for (const auto& data : it->second) {
    uint32_t uuid = 0;
    if (data.size() < sizeof(uuid)) {
      LOG(ERROR) << "Invalid service data, too short";
      ret.clear();
      return ret;
    }
    uuid = data[3] << 24 | data[2] << 16 | data[1] << 8 | data[0];
    ret[util::UuidFromInt32(uuid)].assign(data.begin() + sizeof(uuid),
                                          data.end());
  }

  return ret;
}

LeScanResult::ServiceDataMap LeScanResult::ServiceData128Bit() const {
  ServiceDataMap ret;
  auto it = type_to_data.find(kGapServicesData128bit);
  if (it == type_to_data.end()) {
    return ret;
  }

  for (const auto& data : it->second) {
    bluetooth_v2_shlib::Uuid uuid;
    if (data.size() < sizeof(uuid)) {
      LOG(ERROR) << "Invalid service data, too short";
      ret.clear();
      return ret;
    }
    std::reverse_copy(data.begin(), data.begin() + sizeof(uuid), uuid.begin());
    ret[uuid].assign(data.begin() + sizeof(uuid), data.end());
  }

  return ret;
}

std::map<uint16_t, std::vector<uint8_t>> LeScanResult::ManufacturerData()
    const {
  std::map<uint16_t, std::vector<uint8_t>> ret;
  auto it = type_to_data.find(kGapManufacturerData);
  if (it == type_to_data.end()) {
    return ret;
  }

  for (const auto& data : it->second) {
    uint16_t id = 0;
    if (data.size() < sizeof(id)) {
      LOG(ERROR) << "Invalid manufacturer data, too short";
      ret.clear();
      return ret;
    }
    id = data[1] << 8 | data[0];
    ret[id].assign(data.begin() + sizeof(id), data.end());
  }

  return ret;
}

}  // namespace bluetooth
}  // namespace chromecast