1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_bluetooth_sapphire/internal/host/hci/low_energy_connection.h"
16
17 #include <pw_bytes/endian.h>
18
19 #include <cinttypes>
20
21 #include "pw_bluetooth_sapphire/internal/host/transport/transport.h"
22
23 namespace bt::hci {
24
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)25 LowEnergyConnection::LowEnergyConnection(
26 hci_spec::ConnectionHandle handle,
27 const DeviceAddress& local_address,
28 const DeviceAddress& peer_address,
29 const hci_spec::LEConnectionParameters& params,
30 pw::bluetooth::emboss::ConnectionRole role,
31 const Transport::WeakPtr& hci)
32 : AclConnection(handle, local_address, peer_address, role, hci),
33 WeakSelf(this),
34 parameters_(params) {
35 PW_CHECK(local_address.type() != DeviceAddress::Type::kBREDR);
36 PW_CHECK(peer_address.type() != DeviceAddress::Type::kBREDR);
37 PW_CHECK(hci.is_alive());
38
39 le_ltk_request_id_ = hci->command_channel()->AddLEMetaEventHandler(
40 hci_spec::kLELongTermKeyRequestSubeventCode,
41 fit::bind_member<&LowEnergyConnection::OnLELongTermKeyRequestEvent>(
42 this));
43 }
44
~LowEnergyConnection()45 LowEnergyConnection::~LowEnergyConnection() {
46 // Unregister HCI event handlers.
47 if (hci().is_alive()) {
48 hci()->command_channel()->RemoveEventHandler(le_ltk_request_id_);
49 }
50 }
51
StartEncryption()52 bool LowEnergyConnection::StartEncryption() {
53 if (state() != Connection::State::kConnected) {
54 bt_log(DEBUG, "hci", "connection closed; cannot start encryption");
55 return false;
56 }
57 if (role() != pw::bluetooth::emboss::ConnectionRole::CENTRAL) {
58 bt_log(DEBUG, "hci", "only the central can start encryption");
59 return false;
60 }
61 if (!ltk().has_value()) {
62 bt_log(DEBUG, "hci", "connection has no LTK; cannot start encryption");
63 return false;
64 }
65
66 auto cmd = CommandPacket::New<
67 pw::bluetooth::emboss::LEEnableEncryptionCommandWriter>(
68 hci_spec::kLEStartEncryption);
69 auto params = cmd.view_t();
70 params.connection_handle().Write(handle());
71 params.random_number().Write(ltk()->rand());
72 params.encrypted_diversifier().Write(ltk()->ediv());
73 params.long_term_key().CopyFrom(
74 pw::bluetooth::emboss::LinkKeyView(<k()->value()));
75
76 auto event_cb = [self = GetWeakPtr(), handle = handle()](
77 auto, const EventPacket& event) {
78 if (!self.is_alive()) {
79 return;
80 }
81
82 Result<> result = event.ToResult();
83 if (bt_is_error(result,
84 ERROR,
85 "hci-le",
86 "could not set encryption on link %#.04x",
87 handle)) {
88 if (self->encryption_change_callback()) {
89 self->encryption_change_callback()(result.take_error());
90 }
91 return;
92 }
93 bt_log(DEBUG, "hci-le", "requested encryption start on %#.04x", handle);
94 };
95 if (!hci().is_alive()) {
96 return false;
97 }
98 return hci()->command_channel()->SendCommand(
99 std::move(cmd), std::move(event_cb), hci_spec::kCommandStatusEventCode);
100 }
101
HandleEncryptionStatus(Result<bool> result,bool)102 void LowEnergyConnection::HandleEncryptionStatus(Result<bool> result,
103 bool /*key_refreshed*/) {
104 // "On an authentication failure, the connection shall be automatically
105 // disconnected by the Link Layer." (HCI_LE_Start_Encryption, Vol 2, Part E,
106 // 7.8.24). We make sure of this by telling the controller to disconnect.
107 if (result.is_error()) {
108 Disconnect(pw::bluetooth::emboss::StatusCode::AUTHENTICATION_FAILURE);
109 }
110
111 if (!encryption_change_callback()) {
112 bt_log(DEBUG,
113 "hci",
114 "%#.4x: no encryption status callback assigned",
115 handle());
116 return;
117 }
118 encryption_change_callback()(result);
119 }
120
121 CommandChannel::EventCallbackResult
OnLELongTermKeyRequestEvent(const EventPacket & event)122 LowEnergyConnection::OnLELongTermKeyRequestEvent(const EventPacket& event) {
123 auto view = event.unchecked_view<
124 pw::bluetooth::emboss::LELongTermKeyRequestSubeventView>();
125 if (!view.IsComplete()) {
126 bt_log(WARN, "hci", "malformed LE LTK request event");
127 return CommandChannel::EventCallbackResult::kContinue;
128 }
129
130 hci_spec::ConnectionHandle handle = view.connection_handle().Read();
131
132 // Silently ignore the event as it isn't meant for this connection.
133 if (handle != this->handle()) {
134 return CommandChannel::EventCallbackResult::kContinue;
135 }
136
137 uint64_t rand = view.random_number().Read();
138 uint16_t ediv = view.encrypted_diversifier().Read();
139
140 bt_log(DEBUG,
141 "hci",
142 "LE LTK request - ediv: %#.4x, rand: %#.16" PRIx64,
143 ediv,
144 rand);
145
146 if (!hci().is_alive()) {
147 return CommandChannel::EventCallbackResult::kRemove;
148 }
149
150 auto status_cb = [](auto, const EventPacket& status_event) {
151 HCI_IS_ERROR(
152 status_event, TRACE, "hci-le", "failed to reply to LTK request");
153 };
154
155 if (ltk() && ltk()->rand() == rand && ltk()->ediv() == ediv) {
156 auto cmd = CommandPacket::New<
157 pw::bluetooth::emboss::LELongTermKeyRequestReplyCommandWriter>(
158 hci_spec::kLELongTermKeyRequestReply);
159 auto cmd_view = cmd.view_t();
160 cmd_view.connection_handle().Write(handle);
161 cmd_view.long_term_key().CopyFrom(
162 pw::bluetooth::emboss::LinkKeyView(<k()->value()));
163 hci()->command_channel()->SendCommand(cmd, std::move(status_cb));
164 } else {
165 bt_log(DEBUG, "hci-le", "LTK request rejected");
166
167 auto cmd = CommandPacket::New<
168 pw::bluetooth::emboss::LELongTermKeyRequestNegativeReplyCommandWriter>(
169 hci_spec::kLELongTermKeyRequestNegativeReply);
170 auto cmd_view = cmd.view_t();
171 cmd_view.connection_handle().Write(handle);
172 hci()->command_channel()->SendCommand(cmd, std::move(status_cb));
173 }
174
175 return CommandChannel::EventCallbackResult::kContinue;
176 }
177
178 } // namespace bt::hci
179