| // 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; |
| } |