| // Copyright 2023 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_bluetooth_sapphire/internal/host/hci/low_energy_connection.h" |
| |
| #include "pw_bluetooth_sapphire/internal/host/transport/transport.h" |
| |
| #pragma clang diagnostic ignored "-Wshadow" |
| |
| namespace bt::hci { |
| |
| LowEnergyConnection::LowEnergyConnection( |
| hci_spec::ConnectionHandle handle, |
| const DeviceAddress& local_address, |
| const DeviceAddress& peer_address, |
| const hci_spec::LEConnectionParameters& params, |
| pw::bluetooth::emboss::ConnectionRole role, |
| const Transport::WeakPtr& hci) |
| : AclConnection(handle, local_address, peer_address, role, hci), |
| WeakSelf(this), |
| parameters_(params) { |
| BT_ASSERT(local_address.type() != DeviceAddress::Type::kBREDR); |
| BT_ASSERT(peer_address.type() != DeviceAddress::Type::kBREDR); |
| BT_ASSERT(hci.is_alive()); |
| |
| le_ltk_request_id_ = hci->command_channel()->AddLEMetaEventHandler( |
| hci_spec::kLELongTermKeyRequestSubeventCode, |
| fit::bind_member<&LowEnergyConnection::OnLELongTermKeyRequestEvent>( |
| this)); |
| } |
| |
| LowEnergyConnection::~LowEnergyConnection() { |
| // Unregister HCI event handlers. |
| if (hci().is_alive()) { |
| hci()->command_channel()->RemoveEventHandler(le_ltk_request_id_); |
| } |
| } |
| |
| bool LowEnergyConnection::StartEncryption() { |
| if (state() != Connection::State::kConnected) { |
| bt_log(DEBUG, "hci", "connection closed; cannot start encryption"); |
| return false; |
| } |
| if (role() != pw::bluetooth::emboss::ConnectionRole::CENTRAL) { |
| bt_log(DEBUG, "hci", "only the central can start encryption"); |
| return false; |
| } |
| if (!ltk().has_value()) { |
| bt_log(DEBUG, "hci", "connection has no LTK; cannot start encryption"); |
| return false; |
| } |
| |
| auto cmd = EmbossCommandPacket::New< |
| pw::bluetooth::emboss::LEEnableEncryptionCommandWriter>( |
| hci_spec::kLEStartEncryption); |
| auto params = cmd.view_t(); |
| params.connection_handle().Write(handle()); |
| params.random_number().Write(ltk()->rand()); |
| params.encrypted_diversifier().Write(ltk()->ediv()); |
| params.long_term_key().CopyFrom( |
| pw::bluetooth::emboss::LinkKeyView(<k()->value())); |
| |
| auto event_cb = [self = GetWeakPtr(), handle = handle()]( |
| auto id, const EventPacket& event) { |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| Result<> result = event.ToResult(); |
| if (bt_is_error(result, |
| ERROR, |
| "hci-le", |
| "could not set encryption on link %#.04x", |
| handle)) { |
| if (self->encryption_change_callback()) { |
| self->encryption_change_callback()(result.take_error()); |
| } |
| return; |
| } |
| bt_log(DEBUG, "hci-le", "requested encryption start on %#.04x", handle); |
| }; |
| if (!hci().is_alive()) { |
| return false; |
| } |
| return hci()->command_channel()->SendCommand( |
| std::move(cmd), std::move(event_cb), hci_spec::kCommandStatusEventCode); |
| } |
| |
| void LowEnergyConnection::HandleEncryptionStatus(Result<bool> result, |
| bool /*key_refreshed*/) { |
| // "On an authentication failure, the connection shall be automatically |
| // disconnected by the Link Layer." (HCI_LE_Start_Encryption, Vol 2, Part E, |
| // 7.8.24). We make sure of this by telling the controller to disconnect. |
| if (result.is_error()) { |
| Disconnect(pw::bluetooth::emboss::StatusCode::AUTHENTICATION_FAILURE); |
| } |
| |
| if (!encryption_change_callback()) { |
| bt_log(DEBUG, |
| "hci", |
| "%#.4x: no encryption status callback assigned", |
| handle()); |
| return; |
| } |
| encryption_change_callback()(result); |
| } |
| |
| CommandChannel::EventCallbackResult |
| LowEnergyConnection::OnLELongTermKeyRequestEvent(const EventPacket& event) { |
| BT_ASSERT(event.event_code() == hci_spec::kLEMetaEventCode); |
| BT_ASSERT(event.params<hci_spec::LEMetaEventParams>().subevent_code == |
| hci_spec::kLELongTermKeyRequestSubeventCode); |
| |
| auto* params = |
| event.subevent_params<hci_spec::LELongTermKeyRequestSubeventParams>(); |
| if (!params) { |
| bt_log(WARN, "hci", "malformed LE LTK request event"); |
| return CommandChannel::EventCallbackResult::kContinue; |
| } |
| |
| hci_spec::ConnectionHandle handle = le16toh(params->connection_handle); |
| |
| // Silently ignore the event as it isn't meant for this connection. |
| if (handle != this->handle()) { |
| return CommandChannel::EventCallbackResult::kContinue; |
| } |
| |
| CommandChannel::CommandPacketVariant cmd; |
| |
| uint64_t rand = le64toh(params->random_number); |
| uint16_t ediv = le16toh(params->encrypted_diversifier); |
| |
| bt_log( |
| DEBUG, "hci", "LE LTK request - ediv: %#.4x, rand: %#.16lx", ediv, rand); |
| if (ltk() && ltk()->rand() == rand && ltk()->ediv() == ediv) { |
| cmd = CommandPacket::New( |
| hci_spec::kLELongTermKeyRequestReply, |
| sizeof(hci_spec::LELongTermKeyRequestReplyCommandParams)); |
| auto* params = std::get<std::unique_ptr<CommandPacket>>(cmd) |
| ->mutable_payload< |
| hci_spec::LELongTermKeyRequestReplyCommandParams>(); |
| |
| params->connection_handle = htole16(handle); |
| params->long_term_key = ltk()->value(); |
| } else { |
| bt_log(DEBUG, "hci-le", "LTK request rejected"); |
| |
| cmd = EmbossCommandPacket::New< |
| pw::bluetooth::emboss::LELongTermKeyRequestNegativeReplyCommandWriter>( |
| hci_spec::kLELongTermKeyRequestNegativeReply); |
| auto view = std::get<EmbossCommandPacket>(cmd) |
| .view<pw::bluetooth::emboss:: |
| LELongTermKeyRequestNegativeReplyCommandWriter>(); |
| view.connection_handle().Write(handle); |
| } |
| |
| auto status_cb = [](auto id, const EventPacket& event) { |
| hci_is_error(event, TRACE, "hci-le", "failed to reply to LTK request"); |
| }; |
| if (!hci().is_alive()) { |
| return CommandChannel::EventCallbackResult::kRemove; |
| } |
| hci()->command_channel()->SendCommand(std::move(cmd), std::move(status_cb)); |
| return CommandChannel::EventCallbackResult::kContinue; |
| } |
| |
| } // namespace bt::hci |