blob: 53117a753b5945a24dc252eddfb60dbb5054fef5 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "helpers.h"
#include <endian.h>
#include <algorithm>
#include <iterator>
#include <unordered_set>
#include "fuchsia/bluetooth/sys/cpp/fidl.h"
#include "src/connectivity/bluetooth/core/bt-host/att/att.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/discovery_filter.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/gap.h"
#include "src/connectivity/bluetooth/core/bt-host/sco/sco.h"
#include "src/connectivity/bluetooth/core/bt-host/sm/types.h"
#include "src/lib/fxl/strings/split_string.h"
#include "src/lib/fxl/strings/string_number_conversions.h"
using fuchsia::bluetooth::Bool;
using fuchsia::bluetooth::Error;
using fuchsia::bluetooth::ErrorCode;
using fuchsia::bluetooth::Int8;
using fuchsia::bluetooth::Status;
namespace fble = fuchsia::bluetooth::le;
namespace fbredr = fuchsia::bluetooth::bredr;
namespace fbt = fuchsia::bluetooth;
namespace fgatt = fuchsia::bluetooth::gatt;
namespace fhost = fuchsia::bluetooth::host;
namespace fsys = fuchsia::bluetooth::sys;
namespace faudio = fuchsia::hardware::audio;
namespace bthost::fidl_helpers {
namespace {
fbt::AddressType AddressTypeToFidl(bt::DeviceAddress::Type type) {
switch (type) {
case bt::DeviceAddress::Type::kBREDR:
[[fallthrough]];
case bt::DeviceAddress::Type::kLEPublic:
return fbt::AddressType::PUBLIC;
case bt::DeviceAddress::Type::kLERandom:
[[fallthrough]];
case bt::DeviceAddress::Type::kLEAnonymous:
return fbt::AddressType::RANDOM;
}
return fbt::AddressType::PUBLIC;
}
fbt::Address AddressToFidl(fbt::AddressType type, const bt::DeviceAddressBytes& value) {
fbt::Address output;
output.type = type;
bt::MutableBufferView value_dst(output.bytes.data(), output.bytes.size());
value_dst.Write(value.bytes());
return output;
}
fbt::Address AddressToFidl(const bt::DeviceAddress& input) {
return AddressToFidl(AddressTypeToFidl(input.type()), input.value());
}
bt::sm::SecurityProperties SecurityPropsFromFidl(const fsys::SecurityProperties& sec_prop) {
auto level = bt::sm::SecurityLevel::kEncrypted;
if (sec_prop.authenticated) {
level = bt::sm::SecurityLevel::kAuthenticated;
}
return bt::sm::SecurityProperties(level, sec_prop.encryption_key_size,
sec_prop.secure_connections);
}
fsys::SecurityProperties SecurityPropsToFidl(const bt::sm::SecurityProperties& sec_prop) {
return fsys::SecurityProperties{
.authenticated = sec_prop.authenticated(),
.secure_connections = sec_prop.secure_connections(),
// TODO(armansito): Declare the key size as uint8_t in bt::sm?
.encryption_key_size = static_cast<uint8_t>(sec_prop.enc_key_size()),
};
}
bt::sm::LTK LtkFromFidl(const fsys::Ltk& ltk) {
return bt::sm::LTK(SecurityPropsFromFidl(ltk.key.security),
bt::hci::LinkKey(ltk.key.data.value, ltk.rand, ltk.ediv));
}
fsys::PeerKey LtkToFidlPeerKey(const bt::sm::LTK& ltk) {
return fsys::PeerKey{
.security = SecurityPropsToFidl(ltk.security()),
.data = fsys::Key{ltk.key().value()},
};
}
fsys::Ltk LtkToFidl(const bt::sm::LTK& ltk) {
return fsys::Ltk{
.key = LtkToFidlPeerKey(ltk),
.ediv = ltk.key().ediv(),
.rand = ltk.key().rand(),
};
}
bt::sm::Key PeerKeyFromFidl(const fsys::PeerKey& key) {
return bt::sm::Key(SecurityPropsFromFidl(key.security), key.data.value);
}
fsys::PeerKey PeerKeyToFidl(const bt::sm::Key& key) {
return fsys::PeerKey{
.security = SecurityPropsToFidl(key.security()),
.data = {key.value()},
};
}
fbt::DeviceClass DeviceClassToFidl(bt::DeviceClass input) {
auto bytes = input.bytes();
fbt::DeviceClass output{static_cast<uint32_t>(bytes[0] | (bytes[1] << 8) | (bytes[2] << 16))};
return output;
}
std::optional<bt::sdp::DataElement> FidlToDataElement(const fbredr::DataElement& fidl) {
bt::sdp::DataElement out;
switch (fidl.Which()) {
case fbredr::DataElement::Tag::kInt8:
return bt::sdp::DataElement(fidl.int8());
case fbredr::DataElement::Tag::kInt16:
return bt::sdp::DataElement(fidl.int16());
case fbredr::DataElement::Tag::kInt32:
return bt::sdp::DataElement(fidl.int32());
case fbredr::DataElement::Tag::kInt64:
return bt::sdp::DataElement(fidl.int64());
case fbredr::DataElement::Tag::kUint8:
return bt::sdp::DataElement(fidl.uint8());
case fbredr::DataElement::Tag::kUint16:
return bt::sdp::DataElement(fidl.uint16());
case fbredr::DataElement::Tag::kUint32:
return bt::sdp::DataElement(fidl.uint32());
case fbredr::DataElement::Tag::kUint64:
return bt::sdp::DataElement(fidl.uint64());
case fbredr::DataElement::Tag::kStr:
return bt::sdp::DataElement(fidl.str());
case fbredr::DataElement::Tag::kB:
return bt::sdp::DataElement(fidl.b());
case fbredr::DataElement::Tag::kUuid:
out.Set(fidl_helpers::UuidFromFidl(fidl.uuid()));
case fbredr::DataElement::Tag::kSequence: {
std::vector<bt::sdp::DataElement> seq;
for (const auto& fidl_elem : fidl.sequence()) {
auto it = FidlToDataElement(*fidl_elem);
if (!it) {
return std::nullopt;
}
seq.emplace_back(std::move(it.value()));
}
out.Set(std::move(seq));
break;
}
case fbredr::DataElement::Tag::kAlternatives: {
std::vector<bt::sdp::DataElement> alts;
for (const auto& fidl_elem : fidl.alternatives()) {
auto elem = FidlToDataElement(*fidl_elem);
if (!elem) {
return std::nullopt;
}
alts.emplace_back(std::move(elem.value()));
}
out.SetAlternative(std::move(alts));
break;
}
default:
// Types not handled: Null datatype (never used) and Url data type (not supported by Set)
bt_log(WARN, "fidl", "Encountered FidlToDataElement type not handled.");
return std::nullopt;
}
return out;
}
bool AddProtocolDescriptorList(bt::sdp::ServiceRecord* rec,
bt::sdp::ServiceRecord::ProtocolListId id,
const ::std::vector<fbredr::ProtocolDescriptor>& descriptor_list) {
bt_log(TRACE, "fidl", "ProtocolDescriptorList %d", id);
for (auto& descriptor : descriptor_list) {
bt::sdp::DataElement protocol_params;
if (descriptor.params.size() > 1) {
std::vector<bt::sdp::DataElement> params;
for (auto& fidl_param : descriptor.params) {
auto bt_param = FidlToDataElement(fidl_param);
if (bt_param) {
params.emplace_back(std::move(bt_param.value()));
} else {
return false;
}
}
protocol_params.Set(std::move(params));
} else if (descriptor.params.size() == 1) {
auto param = FidlToDataElement(descriptor.params.front());
if (param) {
protocol_params = std::move(param).value();
} else {
return false;
}
protocol_params = FidlToDataElement(descriptor.params.front()).value();
}
bt_log(TRACE, "fidl", "Adding protocol descriptor: {%d : %s}",
fidl::ToUnderlying(descriptor.protocol), protocol_params.ToString().c_str());
rec->AddProtocolDescriptor(id, bt::UUID(static_cast<uint16_t>(descriptor.protocol)),
std::move(protocol_params));
}
return true;
}
// Returns true if the appearance value (in host byte order) is included in
// fuchsia.bluetooth.Appearance, which is a subset of Bluetooth Assigned Numbers, "Appearance
// Values" (https://www.bluetooth.com/specifications/assigned-numbers/).
//
// TODO(fxbug.dev/66358): Remove this compatibility check with the strict Appearance enum.
[[nodiscard]] bool IsAppearanceValid(uint16_t appearance_raw) {
switch (appearance_raw) {
case 0u: // UNKNOWN
[[fallthrough]];
case 64u: // PHONE
[[fallthrough]];
case 128u: // COMPUTER
[[fallthrough]];
case 192u: // WATCH
[[fallthrough]];
case 193u: // WATCH_SPORTS
[[fallthrough]];
case 256u: // CLOCK
[[fallthrough]];
case 320u: // DISPLAY
[[fallthrough]];
case 384u: // REMOTE_CONTROL
[[fallthrough]];
case 448u: // EYE_GLASSES
[[fallthrough]];
case 512u: // TAG
[[fallthrough]];
case 576u: // KEYRING
[[fallthrough]];
case 640u: // MEDIA_PLAYER
[[fallthrough]];
case 704u: // BARCODE_SCANNER
[[fallthrough]];
case 768u: // THERMOMETER
[[fallthrough]];
case 769u: // THERMOMETER_EAR
[[fallthrough]];
case 832u: // HEART_RATE_SENSOR
[[fallthrough]];
case 833u: // HEART_RATE_SENSOR_BELT
[[fallthrough]];
case 896u: // BLOOD_PRESSURE
[[fallthrough]];
case 897u: // BLOOD_PRESSURE_ARM
[[fallthrough]];
case 898u: // BLOOD_PRESSURE_WRIST
[[fallthrough]];
case 960u: // HID
[[fallthrough]];
case 961u: // HID_KEYBOARD
[[fallthrough]];
case 962u: // HID_MOUSE
[[fallthrough]];
case 963u: // HID_JOYSTICK
[[fallthrough]];
case 964u: // HID_GAMEPAD
[[fallthrough]];
case 965u: // HID_DIGITIZER_TABLET
[[fallthrough]];
case 966u: // HID_CARD_READER
[[fallthrough]];
case 967u: // HID_DIGITAL_PEN
[[fallthrough]];
case 968u: // HID_BARCODE_SCANNER
[[fallthrough]];
case 1024u: // GLUCOSE_METER
[[fallthrough]];
case 1088u: // RUNNING_WALKING_SENSOR
[[fallthrough]];
case 1089u: // RUNNING_WALKING_SENSOR_IN_SHOE
[[fallthrough]];
case 1090u: // RUNNING_WALKING_SENSOR_ON_SHOE
[[fallthrough]];
case 1091u: // RUNNING_WALKING_SENSOR_ON_HIP
[[fallthrough]];
case 1152u: // CYCLING
[[fallthrough]];
case 1153u: // CYCLING_COMPUTER
[[fallthrough]];
case 1154u: // CYCLING_SPEED_SENSOR
[[fallthrough]];
case 1155u: // CYCLING_CADENCE_SENSOR
[[fallthrough]];
case 1156u: // CYCLING_POWER_SENSOR
[[fallthrough]];
case 1157u: // CYCLING_SPEED_AND_CADENCE_SENSOR
[[fallthrough]];
case 3136u: // PULSE_OXIMETER
[[fallthrough]];
case 3137u: // PULSE_OXIMETER_FINGERTIP
[[fallthrough]];
case 3138u: // PULSE_OXIMETER_WRIST
[[fallthrough]];
case 3200u: // WEIGHT_SCALE
[[fallthrough]];
case 3264u: // PERSONAL_MOBILITY
[[fallthrough]];
case 3265u: // PERSONAL_MOBILITY_WHEELCHAIR
[[fallthrough]];
case 3266u: // PERSONAL_MOBILITY_SCOOTER
[[fallthrough]];
case 3328u: // GLUCOSE_MONITOR
[[fallthrough]];
case 5184u: // SPORTS_ACTIVITY
[[fallthrough]];
case 5185u: // SPORTS_ACTIVITY_LOCATION_DISPLAY
[[fallthrough]];
case 5186u: // SPORTS_ACTIVITY_LOCATION_AND_NAV_DISPLAY
[[fallthrough]];
case 5187u: // SPORTS_ACTIVITY_LOCATION_POD
[[fallthrough]];
case 5188u: // SPORTS_ACTIVITY_LOCATION_AND_NAV_POD
return true;
default:
return false;
}
}
[[nodiscard]] std::optional<fbt::Appearance> AppearanceToFidl(uint16_t appearance_raw) {
if (IsAppearanceValid(appearance_raw)) {
return static_cast<fbt::Appearance>(appearance_raw);
}
return std::nullopt;
}
} // namespace
std::optional<bt::PeerId> PeerIdFromString(const std::string& id) {
uint64_t value;
if (!fxl::StringToNumberWithError<decltype(value)>(id, &value, fxl::Base::k16)) {
return std::nullopt;
}
return bt::PeerId(value);
}
std::optional<bt::DeviceAddressBytes> AddressBytesFromString(const std::string& addr) {
if (addr.size() != 17)
return std::nullopt;
auto split = fxl::SplitString(std::string_view(addr.data(), addr.size()), ":",
fxl::kKeepWhitespace, fxl::kSplitWantAll);
if (split.size() != 6)
return std::nullopt;
std::array<uint8_t, 6> bytes;
size_t index = 5;
for (const auto& octet_str : split) {
uint8_t octet;
if (!fxl::StringToNumberWithError<uint8_t>(octet_str, &octet, fxl::Base::k16))
return std::nullopt;
bytes[index--] = octet;
}
return bt::DeviceAddressBytes(bytes);
}
ErrorCode HostErrorToFidlDeprecated(bt::HostError host_error) {
switch (host_error) {
case bt::HostError::kFailed:
return ErrorCode::FAILED;
case bt::HostError::kTimedOut:
return ErrorCode::TIMED_OUT;
case bt::HostError::kInvalidParameters:
return ErrorCode::INVALID_ARGUMENTS;
case bt::HostError::kCanceled:
return ErrorCode::CANCELED;
case bt::HostError::kInProgress:
return ErrorCode::IN_PROGRESS;
case bt::HostError::kNotSupported:
return ErrorCode::NOT_SUPPORTED;
case bt::HostError::kNotFound:
return ErrorCode::NOT_FOUND;
case bt::HostError::kProtocolError:
return ErrorCode::PROTOCOL_ERROR;
default:
break;
}
return ErrorCode::FAILED;
}
Status NewFidlError(ErrorCode error_code, std::string description) {
Status status;
status.error = Error::New();
status.error->error_code = error_code;
status.error->description = description;
return status;
}
fsys::Error HostErrorToFidl(bt::HostError error) {
ZX_DEBUG_ASSERT(error != bt::HostError::kNoError);
switch (error) {
case bt::HostError::kFailed:
return fsys::Error::FAILED;
case bt::HostError::kTimedOut:
return fsys::Error::TIMED_OUT;
case bt::HostError::kInvalidParameters:
return fsys::Error::INVALID_ARGUMENTS;
case bt::HostError::kCanceled:
return fsys::Error::CANCELED;
case bt::HostError::kInProgress:
return fsys::Error::IN_PROGRESS;
case bt::HostError::kNotSupported:
return fsys::Error::NOT_SUPPORTED;
case bt::HostError::kNotFound:
return fsys::Error::PEER_NOT_FOUND;
default:
break;
}
return fsys::Error::FAILED;
}
fuchsia::bluetooth::gatt::Error GattStatusToFidl(bt::Status<bt::att::ErrorCode> status) {
ZX_ASSERT(!status.is_success());
switch (status.error()) {
case bt::HostError::kPacketMalformed:
return fuchsia::bluetooth::gatt::Error::INVALID_RESPONSE;
case bt::HostError::kProtocolError:
switch (status.protocol_error()) {
case bt::att::ErrorCode::kInsufficientAuthorization:
return fuchsia::bluetooth::gatt::Error::INSUFFICIENT_AUTHORIZATION;
case bt::att::ErrorCode::kInsufficientAuthentication:
return fuchsia::bluetooth::gatt::Error::INSUFFICIENT_AUTHENTICATION;
case bt::att::ErrorCode::kInsufficientEncryptionKeySize:
return fuchsia::bluetooth::gatt::Error::INSUFFICIENT_ENCRYPTION_KEY_SIZE;
case bt::att::ErrorCode::kInsufficientEncryption:
return fuchsia::bluetooth::gatt::Error::INSUFFICIENT_ENCRYPTION;
case bt::att::ErrorCode::kReadNotPermitted:
return fuchsia::bluetooth::gatt::Error::READ_NOT_PERMITTED;
default:
return fuchsia::bluetooth::gatt::Error::FAILURE;
}
default:
return fuchsia::bluetooth::gatt::Error::FAILURE;
}
}
bt::UUID UuidFromFidl(const fuchsia::bluetooth::Uuid& input) {
// Conversion must always succeed given the defined size of |input|.
static_assert(sizeof(input.value) == 16, "FIDL UUID definition malformed!");
return bt::UUID(bt::BufferView(input.value.data(), input.value.size()));
}
fuchsia::bluetooth::Uuid UuidToFidl(const bt::UUID& uuid) {
fuchsia::bluetooth::Uuid output;
// Conversion must always succeed given the defined size of |input|.
static_assert(sizeof(output.value) == 16, "FIDL UUID definition malformed!");
output.value = uuid.value();
return output;
}
bt::sm::IOCapability IoCapabilityFromFidl(fsys::InputCapability input,
fsys::OutputCapability output) {
if (input == fsys::InputCapability::NONE && output == fsys::OutputCapability::NONE) {
return bt::sm::IOCapability::kNoInputNoOutput;
} else if (input == fsys::InputCapability::KEYBOARD &&
output == fsys::OutputCapability::DISPLAY) {
return bt::sm::IOCapability::kKeyboardDisplay;
} else if (input == fsys::InputCapability::KEYBOARD && output == fsys::OutputCapability::NONE) {
return bt::sm::IOCapability::kKeyboardOnly;
} else if (input == fsys::InputCapability::NONE && output == fsys::OutputCapability::DISPLAY) {
return bt::sm::IOCapability::kDisplayOnly;
} else if (input == fsys::InputCapability::CONFIRMATION &&
output == fsys::OutputCapability::DISPLAY) {
return bt::sm::IOCapability::kDisplayYesNo;
}
return bt::sm::IOCapability::kNoInputNoOutput;
}
bt::gap::LeSecurityMode LeSecurityModeFromFidl(const fsys::LeSecurityMode mode) {
switch (mode) {
case fsys::LeSecurityMode::MODE_1:
return bt::gap::LeSecurityMode::Mode1;
case fsys::LeSecurityMode::SECURE_CONNECTIONS_ONLY:
return bt::gap::LeSecurityMode::SecureConnectionsOnly;
}
bt_log(WARN, "fidl", "FIDL security mode not recognized, defaulting to SecureConnectionsOnly");
return bt::gap::LeSecurityMode::SecureConnectionsOnly;
}
std::optional<bt::sm::SecurityLevel> SecurityLevelFromFidl(const fsys::PairingSecurityLevel level) {
switch (level) {
case fsys::PairingSecurityLevel::ENCRYPTED:
return bt::sm::SecurityLevel::kEncrypted;
case fsys::PairingSecurityLevel::AUTHENTICATED:
return bt::sm::SecurityLevel::kAuthenticated;
default:
return std::nullopt;
};
}
fsys::TechnologyType TechnologyTypeToFidl(bt::gap::TechnologyType type) {
switch (type) {
case bt::gap::TechnologyType::kLowEnergy:
return fsys::TechnologyType::LOW_ENERGY;
case bt::gap::TechnologyType::kClassic:
return fsys::TechnologyType::CLASSIC;
case bt::gap::TechnologyType::kDualMode:
return fsys::TechnologyType::DUAL_MODE;
default:
ZX_PANIC("invalid technology type: %u", static_cast<unsigned int>(type));
break;
}
// This should never execute.
return fsys::TechnologyType::DUAL_MODE;
}
fsys::HostInfo HostInfoToFidl(const bt::gap::Adapter& adapter) {
fsys::HostInfo info;
info.set_id(fbt::HostId{adapter.identifier().value()});
info.set_technology(TechnologyTypeToFidl(adapter.state().type()));
info.set_address(AddressToFidl(fbt::AddressType::PUBLIC, adapter.state().controller_address()));
info.set_local_name(adapter.local_name());
info.set_discoverable(adapter.IsDiscoverable());
info.set_discovering(adapter.IsDiscovering());
return info;
}
fsys::Peer PeerToFidl(const bt::gap::Peer& peer) {
fsys::Peer output;
output.set_id(fbt::PeerId{peer.identifier().value()});
output.set_address(AddressToFidl(peer.address()));
output.set_technology(TechnologyTypeToFidl(peer.technology()));
output.set_connected(peer.connected());
output.set_bonded(peer.bonded());
if (peer.name()) {
output.set_name(*peer.name());
}
if (peer.le()) {
if (auto adv = bt::AdvertisingData::FromBytes(peer.le()->advertising_data())) {
if (adv->appearance().has_value()) {
if (auto appearance = AppearanceToFidl(adv->appearance().value())) {
output.set_appearance(appearance.value());
} else {
bt_log(DEBUG, "fidl", "omitting unencodeable appearance %#.4x of peer %s",
adv->appearance().value(), bt_str(peer.identifier()));
}
}
if (adv->tx_power()) {
output.set_tx_power(adv->tx_power().value());
}
}
}
if (peer.bredr() && peer.bredr()->device_class()) {
output.set_device_class(DeviceClassToFidl(*peer.bredr()->device_class()));
}
if (peer.rssi() != bt::hci::kRSSIInvalid) {
output.set_rssi(peer.rssi());
}
if (peer.bredr()) {
std::transform(peer.bredr()->services().begin(), peer.bredr()->services().end(),
std::back_inserter(*output.mutable_bredr_services()), UuidToFidl);
}
// TODO(fxbug.dev/57344): Populate le_service UUIDs based on GATT results as well as advertising
// and inquiry data.
return output;
}
std::optional<bt::DeviceAddress> AddressFromFidlBondingData(
const fuchsia::bluetooth::sys::BondingData& bond) {
bt::DeviceAddressBytes bytes(bond.address().bytes);
bt::DeviceAddress::Type type;
if (bond.has_bredr_bond()) {
// A random identity address can only be present in a LE-only bond.
if (bond.address().type == fbt::AddressType::RANDOM) {
bt_log(WARN, "fidl", "BR/EDR or Dual-Mode bond cannot have a random identity address!");
return std::nullopt;
}
// TODO(fxbug.dev/2761): We currently assign kBREDR as the address type for dual-mode bonds.
// This makes address management for dual-mode devices a bit confusing as we have two "public"
// address types (i.e. kBREDR and kLEPublic). We should align the stack address types with
// the FIDL address types, such that both kBREDR and kLEPublic are represented as the same
// kind of "PUBLIC".
type = bt::DeviceAddress::Type::kBREDR;
} else {
type = bond.address().type == fbt::AddressType::RANDOM ? bt::DeviceAddress::Type::kLERandom
: bt::DeviceAddress::Type::kLEPublic;
}
return {bt::DeviceAddress(type, bytes)};
}
bt::sm::PairingData LePairingDataFromFidl(const fsys::LeBondData& data) {
bt::sm::PairingData result;
if (data.has_peer_ltk()) {
result.peer_ltk = LtkFromFidl(data.peer_ltk());
}
if (data.has_local_ltk()) {
result.local_ltk = LtkFromFidl(data.local_ltk());
}
if (data.has_irk()) {
result.irk = PeerKeyFromFidl(data.irk());
}
if (data.has_csrk()) {
result.csrk = PeerKeyFromFidl(data.csrk());
}
return result;
}
std::optional<bt::sm::LTK> BredrKeyFromFidl(const fsys::BredrBondData& data) {
if (!data.has_link_key()) {
return std::nullopt;
}
auto key = PeerKeyFromFidl(data.link_key());
return bt::sm::LTK(key.security(), bt::hci::LinkKey(key.value(), 0, 0));
}
std::vector<bt::UUID> BredrServicesFromFidl(const fuchsia::bluetooth::sys::BredrBondData& data) {
std::vector<bt::UUID> services_out;
if (data.has_services()) {
std::transform(data.services().begin(), data.services().end(), std::back_inserter(services_out),
UuidFromFidl);
}
return services_out;
}
fuchsia::bluetooth::sys::BondingData PeerToFidlBondingData(const bt::gap::Adapter& adapter,
const bt::gap::Peer& peer) {
fsys::BondingData out;
out.set_identifier(fbt::PeerId{peer.identifier().value()});
out.set_local_address(
AddressToFidl(fbt::AddressType::PUBLIC, adapter.state().controller_address()));
out.set_address(AddressToFidl(peer.address()));
if (peer.name()) {
out.set_name(*peer.name());
}
// LE
if (peer.le() && peer.le()->bond_data()) {
fsys::LeBondData out_le;
const bt::sm::PairingData& bond = *peer.le()->bond_data();
// TODO(armansito): Store the peer's preferred connection parameters.
// TODO(fxbug.dev/59645): Store GATT and AD service UUIDs.
if (bond.local_ltk) {
out_le.set_local_ltk(LtkToFidl(*bond.local_ltk));
}
if (bond.peer_ltk) {
out_le.set_peer_ltk(LtkToFidl(*bond.peer_ltk));
}
if (bond.irk) {
out_le.set_irk(PeerKeyToFidl(*bond.irk));
}
if (bond.csrk) {
out_le.set_csrk(PeerKeyToFidl(*bond.csrk));
}
out.set_le_bond(std::move(out_le));
}
// BR/EDR
if (peer.bredr() && peer.bredr()->link_key()) {
fsys::BredrBondData out_bredr;
// TODO(fxbug.dev/1262): Populate with history of role switches.
const auto& services = peer.bredr()->services();
std::transform(services.begin(), services.end(),
std::back_inserter(*out_bredr.mutable_services()), UuidToFidl);
out_bredr.set_link_key(LtkToFidlPeerKey(*peer.bredr()->link_key()));
out.set_bredr_bond(std::move(out_bredr));
}
return out;
}
fble::RemoteDevicePtr NewLERemoteDevice(const bt::gap::Peer& peer) {
bt::AdvertisingData ad;
if (!peer.le()) {
return nullptr;
}
const auto& le = *peer.le();
auto fidl_device = fble::RemoteDevice::New();
fidl_device->identifier = peer.identifier().ToString();
fidl_device->connectable = peer.connectable();
// Initialize advertising data only if its non-empty.
if (le.advertising_data().size() != 0u) {
std::optional<bt::AdvertisingData> ad = bt::AdvertisingData::FromBytes(le.advertising_data());
if (!ad.has_value()) {
return nullptr;
}
auto data = fidl_helpers::AdvertisingDataToFidlDeprecated(ad.value());
fidl_device->advertising_data =
std::make_unique<fble::AdvertisingDataDeprecated>(std::move(data));
}
if (peer.rssi() != bt::hci::kRSSIInvalid) {
fidl_device->rssi = Int8::New();
fidl_device->rssi->value = peer.rssi();
}
return fidl_device;
}
bool IsScanFilterValid(const fble::ScanFilter& fidl_filter) {
// |service_uuids| is the only field that can potentially contain invalid
// data, since they are represented as strings.
if (!fidl_filter.service_uuids)
return true;
for (const auto& uuid_str : *fidl_filter.service_uuids) {
if (!bt::IsStringValidUuid(uuid_str))
return false;
}
return true;
}
bool PopulateDiscoveryFilter(const fble::ScanFilter& fidl_filter,
bt::gap::DiscoveryFilter* out_filter) {
ZX_DEBUG_ASSERT(out_filter);
if (fidl_filter.service_uuids) {
std::vector<bt::UUID> uuids;
for (const auto& uuid_str : *fidl_filter.service_uuids) {
bt::UUID uuid;
if (!bt::StringToUuid(uuid_str, &uuid)) {
bt_log(WARN, "fidl", "invalid service UUID given to scan filter: %s", uuid_str.c_str());
return false;
}
uuids.push_back(uuid);
}
if (!uuids.empty())
out_filter->set_service_uuids(uuids);
}
if (fidl_filter.connectable) {
out_filter->set_connectable(fidl_filter.connectable->value);
}
if (fidl_filter.manufacturer_identifier) {
out_filter->set_manufacturer_code(fidl_filter.manufacturer_identifier->value);
}
if (fidl_filter.name_substring && !fidl_filter.name_substring.value_or("").empty()) {
out_filter->set_name_substring(fidl_filter.name_substring.value_or(""));
}
if (fidl_filter.max_path_loss) {
out_filter->set_pathloss(fidl_filter.max_path_loss->value);
}
return true;
}
bt::gap::AdvertisingInterval AdvertisingIntervalFromFidl(fble::AdvertisingModeHint mode_hint) {
switch (mode_hint) {
case fble::AdvertisingModeHint::VERY_FAST:
return bt::gap::AdvertisingInterval::FAST1;
case fble::AdvertisingModeHint::FAST:
return bt::gap::AdvertisingInterval::FAST2;
case fble::AdvertisingModeHint::SLOW:
return bt::gap::AdvertisingInterval::SLOW;
}
return bt::gap::AdvertisingInterval::SLOW;
}
std::optional<bt::AdvertisingData> AdvertisingDataFromFidl(const fble::AdvertisingData& input) {
bt::AdvertisingData output;
if (input.has_name()) {
if (!output.SetLocalName(input.name())) {
return std::nullopt;
}
}
if (input.has_appearance()) {
output.SetAppearance(static_cast<uint16_t>(input.appearance()));
}
if (input.has_tx_power_level()) {
output.SetTxPower(input.tx_power_level());
}
if (input.has_service_uuids()) {
for (const auto& uuid : input.service_uuids()) {
bt::UUID bt_uuid = UuidFromFidl(uuid);
if (!output.AddServiceUuid(bt_uuid)) {
bt_log(WARN, "fidl",
"Received more Service UUIDs than fit in a single AD - truncating UUID %s",
bt_str(bt_uuid));
}
}
}
if (input.has_service_data()) {
for (const auto& entry : input.service_data()) {
if (!output.SetServiceData(UuidFromFidl(entry.uuid), bt::BufferView(entry.data))) {
return std::nullopt;
}
}
}
if (input.has_manufacturer_data()) {
for (const auto& entry : input.manufacturer_data()) {
bt::BufferView data(entry.data);
if (!output.SetManufacturerData(entry.company_id, data)) {
return std::nullopt;
}
}
}
if (input.has_uris()) {
for (const auto& uri : input.uris()) {
if (!output.AddUri(uri)) {
return std::nullopt;
}
}
}
return output;
}
fble::AdvertisingData AdvertisingDataToFidl(const bt::AdvertisingData& input) {
fble::AdvertisingData output;
if (input.local_name()) {
output.set_name(*input.local_name());
}
if (input.appearance()) {
// TODO(fxbug.dev/66358): Remove this to allow for passing arbitrary appearance values to
// clients in a way that's forward-compatible with future BLE revisions.
const uint16_t appearance_raw = input.appearance().value();
if (auto appearance = AppearanceToFidl(appearance_raw)) {
output.set_appearance(appearance.value());
} else {
bt_log(DEBUG, "fidl", "omitting unencodeable appearance %#.4x of peer %s", appearance_raw,
input.local_name().value_or("").c_str());
}
}
if (input.tx_power()) {
output.set_tx_power_level(*input.tx_power());
}
std::unordered_set<bt::UUID> service_uuids = input.service_uuids();
if (!service_uuids.empty()) {
std::vector<fbt::Uuid> uuids;
uuids.reserve(service_uuids.size());
for (const auto& uuid : service_uuids) {
uuids.push_back(fbt::Uuid{uuid.value()});
}
output.set_service_uuids(std::move(uuids));
}
if (!input.service_data_uuids().empty()) {
std::vector<fble::ServiceData> entries;
for (const auto& uuid : input.service_data_uuids()) {
auto data = input.service_data(uuid);
fble::ServiceData entry{fbt::Uuid{uuid.value()}, data.ToVector()};
entries.push_back(std::move(entry));
}
output.set_service_data(std::move(entries));
}
if (!input.manufacturer_data_ids().empty()) {
std::vector<fble::ManufacturerData> entries;
for (const auto& id : input.manufacturer_data_ids()) {
auto data = input.manufacturer_data(id);
fble::ManufacturerData entry{id, data.ToVector()};
entries.push_back(std::move(entry));
}
output.set_manufacturer_data(std::move(entries));
}
if (!input.uris().empty()) {
std::vector<std::string> uris;
for (const auto& uri : input.uris()) {
uris.push_back(uri);
}
output.set_uris(std::move(uris));
}
return output;
}
fble::AdvertisingDataDeprecated AdvertisingDataToFidlDeprecated(const bt::AdvertisingData& input) {
fble::AdvertisingDataDeprecated output;
if (input.local_name()) {
output.name = *input.local_name();
}
if (input.appearance()) {
output.appearance = fbt::UInt16::New();
output.appearance->value = *input.appearance();
}
if (input.tx_power()) {
output.tx_power_level = fbt::Int8::New();
output.tx_power_level->value = *input.tx_power();
}
if (!input.service_uuids().empty()) {
output.service_uuids.emplace();
for (const auto& uuid : input.service_uuids()) {
output.service_uuids->push_back(uuid.ToString());
}
}
if (!input.service_data_uuids().empty()) {
output.service_data.emplace();
for (const auto& uuid : input.service_data_uuids()) {
auto data = input.service_data(uuid);
fble::ServiceDataEntry entry{uuid.ToString(), data.ToVector()};
output.service_data->push_back(std::move(entry));
}
}
if (!input.manufacturer_data_ids().empty()) {
output.manufacturer_specific_data.emplace();
for (const auto& id : input.manufacturer_data_ids()) {
auto data = input.manufacturer_data(id);
fble::ManufacturerSpecificDataEntry entry{id, data.ToVector()};
output.manufacturer_specific_data->push_back(std::move(entry));
}
}
if (!input.uris().empty()) {
output.uris.emplace();
for (const auto& uri : input.uris()) {
output.uris->push_back(uri);
}
}
return output;
}
fble::Peer PeerToFidlLe(const bt::gap::Peer& peer) {
ZX_ASSERT(peer.le());
fble::Peer output;
output.set_id(fbt::PeerId{peer.identifier().value()});
output.set_connectable(peer.connectable());
if (peer.rssi() != bt::hci::kRSSIInvalid) {
output.set_rssi(peer.rssi());
}
if (peer.le()->advertising_data().size() != 0u) {
// We populate |output|'s AdvertisingData field if we can parse the payload. We leave it blank
// otherwise.
if (auto unpacked = bt::AdvertisingData::FromBytes(peer.le()->advertising_data())) {
output.set_advertising_data(fidl_helpers::AdvertisingDataToFidl(unpacked.value()));
}
}
return output;
}
bt::gatt::ReliableMode ReliableModeFromFidl(const fgatt::WriteOptions& write_options) {
return (write_options.has_reliable_mode() &&
write_options.reliable_mode() == fgatt::ReliableMode::ENABLED)
? bt::gatt::ReliableMode::kEnabled
: bt::gatt::ReliableMode::kDisabled;
}
// TODO(fxbug.dev/63438): The 64 bit `fidl_gatt_id` can overflow the 16 bits of a bt:att::Handle
// that underlies CharacteristicHandles when directly casted. Fix this.
bt::gatt::CharacteristicHandle CharacteristicHandleFromFidl(uint64_t fidl_gatt_id) {
if (fidl_gatt_id > std::numeric_limits<bt::att::Handle>::max()) {
bt_log(ERROR, "fidl",
"Casting a 64-bit FIDL GATT ID with `bits[16, 63] != 0` (0x%lX) to 16-bit "
"Characteristic Handle",
fidl_gatt_id);
}
return bt::gatt::CharacteristicHandle(static_cast<bt::att::Handle>(fidl_gatt_id));
}
// TODO(fxbug.dev/63438): The 64 bit `fidl_gatt_id` can overflow the 16 bits of a bt:att::Handle
// that underlies DescriptorHandles when directly casted. Fix this.
bt::gatt::DescriptorHandle DescriptorHandleFromFidl(uint64_t fidl_gatt_id) {
if (fidl_gatt_id > std::numeric_limits<bt::att::Handle>::max()) {
bt_log(ERROR, "fidl",
"Casting a 64-bit FIDL GATT ID with `bits[16, 63] != 0` (0x%lX) to 16-bit Descriptor "
"Handle",
fidl_gatt_id);
}
return bt::gatt::DescriptorHandle(static_cast<bt::att::Handle>(fidl_gatt_id));
}
fit::result<bt::sdp::ServiceRecord, fuchsia::bluetooth::ErrorCode> ServiceDefinitionToServiceRecord(
const fuchsia::bluetooth::bredr::ServiceDefinition& definition) {
bt::sdp::ServiceRecord rec;
std::vector<bt::UUID> classes;
if (!definition.has_service_class_uuids()) {
bt_log(WARN, "fidl", "Advertised service contains no Service UUIDs");
return fit::error(fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS);
}
for (auto& uuid : definition.service_class_uuids()) {
bt::UUID btuuid = fidl_helpers::UuidFromFidl(uuid);
bt_log(TRACE, "fidl", "Setting Service Class UUID %s", bt_str(btuuid));
classes.emplace_back(std::move(btuuid));
}
rec.SetServiceClassUUIDs(classes);
if (definition.has_protocol_descriptor_list()) {
if (!AddProtocolDescriptorList(&rec, bt::sdp::ServiceRecord::kPrimaryProtocolList,
definition.protocol_descriptor_list())) {
bt_log(ERROR, "fidl", "Failed to add protocol descriptor list");
return fit::error(fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS);
}
}
if (definition.has_additional_protocol_descriptor_lists()) {
// It's safe to iterate through this list with a ProtocolListId as ProtocolListId = uint8_t,
// and std::numeric_limits<uint8_t>::max() == 255 == the MAX_SEQUENCE_LENGTH vector limit from
// fuchsia.bluetooth.bredr/ServiceDefinition.additional_protocol_descriptor_lists.
ZX_ASSERT(definition.additional_protocol_descriptor_lists().size() <=
std::numeric_limits<bt::sdp::ServiceRecord::ProtocolListId>::max());
bt::sdp::ServiceRecord::ProtocolListId protocol_list_id = 1;
for (const auto& descriptor_list : definition.additional_protocol_descriptor_lists()) {
if (!AddProtocolDescriptorList(&rec, protocol_list_id, descriptor_list)) {
bt_log(ERROR, "fidl", "Failed to add additional protocol descriptor list");
return fit::error(fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS);
}
protocol_list_id++;
}
}
if (definition.has_profile_descriptors()) {
for (const auto& profile : definition.profile_descriptors()) {
bt_log(TRACE, "fidl", "Adding Profile %#hx v%d.%d", profile.profile_id, profile.major_version,
profile.minor_version);
rec.AddProfile(bt::UUID(uint16_t(profile.profile_id)), profile.major_version,
profile.minor_version);
}
}
if (definition.has_information()) {
for (const auto& info : definition.information()) {
if (!info.has_language()) {
return fit::error(fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS);
}
std::string language = info.language();
std::string name, description, provider;
if (info.has_name()) {
name = info.name();
}
if (info.has_description()) {
description = info.description();
}
if (info.has_provider()) {
provider = info.provider();
}
bt_log(TRACE, "fidl", "Adding Info (%s): (%s, %s, %s)", language.c_str(), name.c_str(),
description.c_str(), provider.c_str());
rec.AddInfo(language, name, description, provider);
}
}
if (definition.has_additional_attributes()) {
for (const auto& attribute : definition.additional_attributes()) {
auto elem = FidlToDataElement(attribute.element);
if (elem) {
bt_log(TRACE, "fidl", "Adding attribute %#x : %s", attribute.id,
elem.value().ToString().c_str());
rec.SetAttribute(attribute.id, std::move(elem.value()));
}
}
}
return fit::ok(std::move(rec));
}
bt::gap::BrEdrSecurityRequirements FidlToBrEdrSecurityRequirements(
const fbredr::ChannelParameters& fidl) {
bt::gap::BrEdrSecurityRequirements requirements{.authentication = false,
.secure_connections = false};
if (fidl.has_security_requirements()) {
if (fidl.security_requirements().has_authentication_required()) {
requirements.authentication = fidl.security_requirements().authentication_required();
}
if (fidl.security_requirements().has_secure_connections_required()) {
requirements.secure_connections = fidl.security_requirements().secure_connections_required();
}
}
return requirements;
}
bt::sco::ParameterSet FidlToScoParameterSet(const fbredr::HfpParameterSet param_set) {
switch (param_set) {
case fbredr::HfpParameterSet::MSBC_T1:
return bt::sco::kParameterSetMsbcT1;
case fbredr::HfpParameterSet::MSBC_T2:
return bt::sco::kParameterSetMsbcT2;
case fbredr::HfpParameterSet::CVSD_S1:
return bt::sco::kParameterSetCvsdS1;
case fbredr::HfpParameterSet::CVSD_S2:
return bt::sco::kParameterSetCvsdS2;
case fbredr::HfpParameterSet::CVSD_S3:
return bt::sco::kParameterSetCvsdS3;
case fbredr::HfpParameterSet::CVSD_S4:
return bt::sco::kParameterSetCvsdS4;
case fbredr::HfpParameterSet::CVSD_D0:
return bt::sco::kParameterSetCvsdD0;
case fbredr::HfpParameterSet::CVSD_D1:
return bt::sco::kParameterSetCvsdD1;
}
}
bt::hci::VendorCodingFormat FidlToScoCodingFormat(const fbredr::CodingFormat format) {
bt::hci::VendorCodingFormat out;
// Set to 0 since vendor specific coding formats are not supported.
out.company_id = 0;
out.vendor_codec_id = 0;
switch (format) {
case fbredr::CodingFormat::ALAW:
out.coding_format = bt::hci::CodingFormat::kALaw;
break;
case fbredr::CodingFormat::MULAW:
out.coding_format = bt::hci::CodingFormat::kMuLaw;
break;
case fbredr::CodingFormat::CVSD:
out.coding_format = bt::hci::CodingFormat::kCvsd;
break;
case fbredr::CodingFormat::LINEAR_PCM:
out.coding_format = bt::hci::CodingFormat::kLinearPcm;
break;
case fbredr::CodingFormat::MSBC:
out.coding_format = bt::hci::CodingFormat::kMSbc;
break;
case fbredr::CodingFormat::TRANSPARENT:
out.coding_format = bt::hci::CodingFormat::kTransparent;
break;
}
return out;
}
fit::result<bt::hci::PcmDataFormat> FidlToPcmDataFormat(const faudio::SampleFormat& format) {
switch (format) {
case faudio::SampleFormat::PCM_SIGNED:
return fit::ok(bt::hci::PcmDataFormat::k2sComplement);
case faudio::SampleFormat::PCM_UNSIGNED:
return fit::ok(bt::hci::PcmDataFormat::kUnsigned);
default:
// Other sample formats are not supported by SCO.
return fit::error();
}
}
bt::hci::ScoDataPath FidlToScoDataPath(const fbredr::DataPath& path) {
switch (path) {
case fbredr::DataPath::HOST:
return bt::hci::ScoDataPath::kHci;
case fbredr::DataPath::OFFLOAD: {
// TODO(fxbug.dev/58458): Use path from stack configuration file instead of this hardcoded
// value. "6" is the data path usually used in Broadcom controllers.
return static_cast<bt::hci::ScoDataPath>(6);
}
case fbredr::DataPath::TEST:
return bt::hci::ScoDataPath::kAudioTestMode;
}
}
fit::result<bt::hci::SynchronousConnectionParameters> FidlToScoParameters(
const fbredr::ScoConnectionParameters& params) {
bt::hci::SynchronousConnectionParameters out;
if (!params.has_parameter_set()) {
bt_log(WARN, "fidl", "SCO parameters missing parameter_set");
return fit::error();
}
auto param_set = FidlToScoParameterSet(params.parameter_set());
out.transmit_bandwidth = param_set.transmit_receive_bandwidth;
out.receive_bandwidth = out.transmit_bandwidth;
if (!params.has_air_coding_format()) {
bt_log(WARN, "fidl", "SCO parameters missing air_coding_format");
return fit::error();
}
auto air_coding_format = FidlToScoCodingFormat(params.air_coding_format());
out.transmit_coding_format = air_coding_format;
out.receive_coding_format = out.transmit_coding_format;
if (!params.has_air_frame_size()) {
bt_log(WARN, "fidl", "SCO parameters missing air_frame_size");
return fit::error();
}
out.transmit_codec_frame_size_bytes = params.air_frame_size();
out.receive_codec_frame_size_bytes = out.transmit_codec_frame_size_bytes;
if (!params.has_io_bandwidth()) {
bt_log(WARN, "fidl", "SCO parameters missing io_bandwidth");
return fit::error();
}
out.input_bandwidth = params.io_bandwidth();
out.output_bandwidth = out.input_bandwidth;
if (!params.has_io_coding_format()) {
bt_log(WARN, "fidl", "SCO parameters missing io_coding_format");
return fit::error();
}
out.input_coding_format = FidlToScoCodingFormat(params.io_coding_format());
out.output_coding_format = out.input_coding_format;
if (!params.has_io_frame_size()) {
bt_log(WARN, "fidl", "SCO parameters missing io_frame_size");
return fit::error();
}
out.input_coded_data_size_bits = params.io_frame_size();
out.output_coded_data_size_bits = out.input_coded_data_size_bits;
if (params.has_io_pcm_data_format() &&
out.input_coding_format.coding_format == bt::hci::CodingFormat::kLinearPcm) {
auto io_pcm_format = FidlToPcmDataFormat(params.io_pcm_data_format());
if (io_pcm_format.is_error()) {
bt_log(WARN, "fidl", "Unsupported IO PCM data format in SCO parameters");
return fit::error();
}
out.input_pcm_data_format = io_pcm_format.value();
out.output_pcm_data_format = out.input_pcm_data_format;
} else if (out.input_coding_format.coding_format == bt::hci::CodingFormat::kLinearPcm) {
bt_log(WARN, "fidl",
"SCO parameters missing io_pcm_data_format (required for linear PCM IO coding format)");
return fit::error();
} else {
out.input_pcm_data_format = bt::hci::PcmDataFormat::kNotApplicable;
out.output_pcm_data_format = out.input_pcm_data_format;
}
if (params.has_io_pcm_sample_payload_msb_position() &&
out.input_coding_format.coding_format == bt::hci::CodingFormat::kLinearPcm) {
out.input_pcm_sample_payload_msb_position = params.io_pcm_sample_payload_msb_position();
out.output_pcm_sample_payload_msb_position = out.input_pcm_sample_payload_msb_position;
} else {
out.input_pcm_sample_payload_msb_position = 0u;
out.output_pcm_sample_payload_msb_position = out.input_pcm_sample_payload_msb_position;
}
if (!params.has_path()) {
bt_log(WARN, "fidl", "SCO parameters missing data path");
return fit::error();
}
out.input_data_path = FidlToScoDataPath(params.path());
out.output_data_path = out.input_data_path;
// For HCI Host transport the transport unit size should be "0". For PCM transport the unit size
// is vendor specific. A unit size of "0" indicates "not applicable".
// TODO(fxbug.dev/58458): Use unit size from stack configuration file instead of hardcoding "not
// applicable".
out.input_transport_unit_size_bits = 0u;
out.output_transport_unit_size_bits = out.input_transport_unit_size_bits;
out.max_latency_ms = param_set.max_latency_ms;
out.packet_types = param_set.packet_types;
out.retransmission_effort = param_set.retransmission_effort;
return fit::ok(out);
}
} // namespace bthost::fidl_helpers
// static
std::vector<uint8_t> fidl::TypeConverter<std::vector<uint8_t>, bt::ByteBuffer>::Convert(
const bt::ByteBuffer& from) {
std::vector<uint8_t> to(from.size());
bt::MutableBufferView view(to.data(), to.size());
view.Write(from);
return to;
}