Direct-BT v3.3.0-1-gc2d430c
Direct-BT - Direct Bluetooth Programming.
SMPHandler.cpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2020 Gothel Software e.K.
4 * Copyright (c) 2020 ZAFENA AB
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25
26#include <cstring>
27#include <string>
28#include <memory>
29#include <cstdint>
30#include <vector>
31#include <cstdio>
32
33#include <algorithm>
34
35extern "C" {
36 #include <unistd.h>
37 #include <sys/socket.h>
38 #include <poll.h>
39 #include <signal.h>
40}
41
42// #define PERF_PRINT_ON 1
43// PERF2_PRINT_ON for read/write single values
44// #define PERF2_PRINT_ON 1
45// PERF3_PRINT_ON for disconnect
46// #define PERF3_PRINT_ON 1
47#include <jau/debug.hpp>
48
49#include <jau/basic_algos.hpp>
50
51#include "L2CAPIoctl.hpp"
52
53#include "SMPHandler.hpp"
54
55#include "BTDevice.hpp"
56#include "BTAdapter.hpp"
57#include "DBTConst.hpp"
58
59using namespace direct_bt;
60using namespace jau::fractions_i64_literals;
61
62SMPEnv::SMPEnv() noexcept
63: exploding( jau::environment::getExplodingProperties("direct_bt.smp") ),
64 SMP_READ_COMMAND_REPLY_TIMEOUT( jau::environment::getInt32Property("direct_bt.smp.cmd.read.timeout", 500, 250 /* min */, INT32_MAX /* max */) ),
65 SMP_WRITE_COMMAND_REPLY_TIMEOUT( jau::environment::getInt32Property("direct_bt.smp.cmd.write.timeout", 500, 250 /* min */, INT32_MAX /* max */) ),
66 SMPPDU_RING_CAPACITY( jau::environment::getInt32Property("direct_bt.smp.ringsize", 128, 64 /* min */, 1024 /* max */) ),
67 DEBUG_DATA( jau::environment::getBooleanProperty("direct_bt.debug.smp.data", false) )
68{
69}
70
72
73std::shared_ptr<BTDevice> SMPHandler::getDeviceChecked() const {
74 std::shared_ptr<BTDevice> ref = wbr_device.lock();
75 if( nullptr == ref ) {
76 throw jau::IllegalStateException("SMPHandler's device already destructed: "+deviceString, E_FILE_LINE);
77 }
78 return ref;
79}
80
81bool SMPHandler::validateConnected() noexcept {
82 bool l2capIsConnected = l2cap.is_open();
83 bool l2capHasIOError = l2cap.hasIOError();
84
85 if( has_ioerror || l2capHasIOError ) {
86 has_ioerror = true; // propagate l2capHasIOError -> has_ioerror
87 ERR_PRINT("ioerr state: GattHandler %s, l2cap %s: %s",
88 getStateString().c_str(), l2cap.getStateString().c_str(), deviceString.c_str());
89 return false;
90 }
91
92 if( !is_connected || !l2capIsConnected ) {
93 ERR_PRINT("Disconnected state: GattHandler %s, l2cap %s: %s",
94 getStateString().c_str(), l2cap.getStateString().c_str(), deviceString.c_str());
95 return false;
96 }
97 return true;
98}
99
100void SMPHandler::smpReaderWork(jau::service_runner& sr) noexcept {
101 jau::snsize_t len;
102 if( !validateConnected() ) {
103 ERR_PRINT("SMPHandler::reader: Invalid IO state -> Stop");
104 sr.set_shall_stop();
105 return;
106 }
107
108 len = l2cap.read(rbuffer.get_wptr(), rbuffer.size());
109 if( 0 < len ) {
110 std::unique_ptr<const SMPPDUMsg> smpPDU = SMPPDUMsg::getSpecialized(rbuffer.get_ptr(), static_cast<jau::nsize_t>(len));
111 const SMPPDUMsg::Opcode opc = smpPDU->getOpcode();
112
114 COND_PRINT(env.DEBUG_DATA, "SMPHandler-IO RECV (SEC_REQ) %s", smpPDU->toString().c_str());
115 jau::for_each_fidelity(smpSecurityReqCallbackList, [&](SMPSecurityReqCallback &cb) {
116 cb(*smpPDU);
117 });
118 } else {
119 COND_PRINT(env.DEBUG_DATA, "SMPHandler-IO RECV (MSG) %s", smpPDU->toString().c_str());
120 if( smpPDURing.isFull() ) {
121 const jau::nsize_t dropCount = smpPDURing.capacity()/4;
122 smpPDURing.drop(dropCount);
123 WARN_PRINT("SMPHandler-IO RECV Drop (%u oldest elements of %u capacity, ring full)", dropCount, smpPDURing.capacity());
124 }
125 if( !smpPDURing.putBlocking( std::move(smpPDU), 0_s ) ) {
126 ERR_PRINT2("smpPDURing put: %s", smpPDURing.toString().c_str());
127 sr.set_shall_stop();
128 return;
129 }
130 }
132 WORDY_PRINT("SMPHandler::reader: l2cap read: IRQed res %d (%s); %s",
133 len, L2CAPClient::getRWExitCodeString(len).c_str(), getStateString().c_str());
134 if( !sr.shall_stop() ) {
135 // need to stop service_runner if interrupted externally
136 sr.set_shall_stop();
137 }
139 len != L2CAPClient::number(L2CAPClient::RWExitCode::READ_TIMEOUT) ) { // expected TIMEOUT if idle
140 if( 0 > len ) { // actual error case
141 IRQ_PRINT("SMPHandler::reader: l2cap read: Error res %d (%s); %s",
142 len, L2CAPClient::getRWExitCodeString(len).c_str(), getStateString().c_str());
143 sr.set_shall_stop();
144 has_ioerror = true;
145 } else { // zero size
146 WORDY_PRINT("SMPHandler::reader: l2cap read: Zero res %d (%s); %s",
147 len, L2CAPClient::getRWExitCodeString(len).c_str(), getStateString().c_str());
148 }
149 }
150}
151
152void SMPHandler::smpReaderEndLocked(jau::service_runner& sr) noexcept {
153 (void)sr;
154 WORDY_PRINT("SMPHandler::reader: Ended. Ring has %u entries flushed", smpPDURing.size());
155 smpPDURing.clear();
156#if 0
157 // Disabled: BT host is sending out disconnect -> simplify tear down
158 if( has_ioerror ) {
159 // Don't rely on receiving a disconnect
160 BTDeviceRef device = getDeviceUnchecked();
161 if( nullptr != device ) {
163 dc.detach();
164 }
165 }
166#endif
167}
168
169SMPHandler::SMPHandler(const std::shared_ptr<BTDevice> &device) noexcept
170: env(SMPEnv::get()),
171 wbr_device(device), deviceString(device->getAddressAndType().toString()),
172 rbuffer(number(Defaults::SMP_MTU_BUFFER_SZ), jau::lb_endian_t::little),
173 l2cap(device->getAdapter().dev_id, device->getAdapter().getAddressAndType(), L2CAP_PSM::UNDEFINED, L2CAP_CID::SMP),
174 is_connected(l2cap.open(*device)), has_ioerror(false),
175 smp_reader_service("SMPHandler::reader", THREAD_SHUTDOWN_TIMEOUT_MS,
176 jau::bind_member(this, &SMPHandler::smpReaderWork),
178 jau::bind_member(this, &SMPHandler::smpReaderEndLocked)),
179 smpPDURing(env.SMPPDU_RING_CAPACITY),
180 mtu(number(Defaults::MIN_SMP_MTU))
181{
182 if( !validateConnected() ) {
183 ERR_PRINT("SMPHandler.ctor: L2CAP could not connect");
184 is_connected = false;
185 return;
186 }
187
188 l2cap.set_interrupted_query( jau::bind_member(&smp_reader_service, &jau::service_runner::shall_stop2) );
189 smp_reader_service.start();
190
191 DBG_PRINT("SMPHandler::ctor: Started: SMPHandler[%s], l2cap[%s]: %s",
192 getStateString().c_str(), l2cap.getStateString().c_str(), deviceString.c_str());
193
194 // FIXME: Determine proper MTU usage: Defaults::MIN_SMP_MTU or Defaults::LE_SECURE_SMP_MTU (if enabled)
195 uint16_t mtu_ = number(Defaults::MIN_SMP_MTU);
196 mtu = std::min(number(Defaults::LE_SECURE_SMP_MTU), (int)mtu_);
197}
198
200 disconnect(false /* disconnectDevice */, false /* ioErrorCause */);
201 clearAllCallbacks();
202}
203
205 // FIXME: Start negotiating security!
206 // FIXME: Return true only if security has been established (encryption and optionally authentication)
207 (void)sec_level;
208 return false;
209}
210
211bool SMPHandler::disconnect(const bool disconnectDevice, const bool ioErrorCause) noexcept {
212 PERF3_TS_T0();
213
214 // Avoid disconnect re-entry -> potential deadlock
215 bool expConn = true; // C++11, exp as value since C++20
216 if( !is_connected.compare_exchange_strong(expConn, false) ) {
217 // not connected
218 const bool smp_service_stopped = smp_reader_service.join(); // [data] race: wait until disconnecting thread has stopped service
219 l2cap.close();
220 DBG_PRINT("SMPHandler::disconnect: Not connected: disconnectDevice %d, ioErrorCause %d: GattHandler[%s], l2cap[%s], stopped %d: %s",
221 disconnectDevice, ioErrorCause, getStateString().c_str(), l2cap.getStateString().c_str(),
222 smp_service_stopped, deviceString.c_str());
223 clearAllCallbacks();
224 return false;
225 }
226
227 PERF3_TS_TD("SMPHandler::disconnect.1");
228 const bool smp_service_stop_res = smp_reader_service.stop();
229 l2cap.close();
230 PERF3_TS_TD("SMPHandler::disconnect.2");
231
232 // Lock to avoid other threads using instance while disconnecting
233 const std::lock_guard<std::recursive_mutex> lock(mtx_command); // RAII-style acquire and relinquish via destructor
234 DBG_PRINT("SMPHandler::disconnect: Start: disconnectDevice %d, ioErrorCause %d: GattHandler[%s], l2cap[%s]: %s",
235 disconnectDevice, ioErrorCause, getStateString().c_str(), l2cap.getStateString().c_str(), deviceString.c_str());
236 clearAllCallbacks();
237
238 DBG_PRINT("SMPHandler::disconnect: End: stopped %d, %s", smp_service_stop_res, deviceString.c_str());
239
240 if( disconnectDevice ) {
241 std::shared_ptr<BTDevice> device = getDeviceUnchecked();
242 if( nullptr != device ) {
243 // Cleanup device resources, proper connection state
244 // Intentionally giving the POWER_OFF reason for the device in case of ioErrorCause!
245 const HCIStatusCode reason = ioErrorCause ?
248 device->disconnect(reason);
249 }
250 }
251 return true;
252}
253
254void SMPHandler::send(const SMPPDUMsg & msg) {
255 if( !validateConnected() ) {
256 throw jau::IllegalStateException("SMPHandler::send: Invalid IO State: req "+msg.toString()+" to "+deviceString, E_FILE_LINE);
257 }
258 if( msg.pdu.size() > mtu ) {
259 throw jau::IllegalArgumentException("clientMaxMTU "+std::to_string(msg.pdu.size())+" > usedMTU "+std::to_string(mtu)+
260 " to "+deviceString, E_FILE_LINE);
261 }
262
263 // Thread safe l2cap.write(..) operation..
264 const jau::snsize_t len = l2cap.write(msg.pdu.get_ptr(), msg.pdu.size());
265 if( len != L2CAPClient::number(L2CAPClient::RWExitCode::INTERRUPTED) ) { // expected exits
266 if( 0 > len ) {
267 ERR_PRINT("l2cap write: Error res %d (%s); %s; %s -> disconnect: %s",
268 len, L2CAPClient::getRWExitCodeString(len).c_str(), getStateString().c_str(),
269 msg.toString().c_str(), deviceString.c_str());
270 has_ioerror = true;
271 disconnect(true /* disconnect_device */, true /* ioerr_cause */); // state -> Disconnected
272 throw BTException("SMPHandler::send: l2cap write: Error: req "+msg.toString()+" -> disconnect: "+deviceString, E_FILE_LINE);
273 }
274 if( static_cast<size_t>(len) != msg.pdu.size() ) {
275 ERR_PRINT("l2cap write: Error: Message size has %d != exp %zu: %s -> disconnect: %s",
276 len, msg.pdu.size(), msg.toString().c_str(), deviceString.c_str());
277 has_ioerror = true;
278 disconnect(true /* disconnect_device */, true /* ioerr_cause */); // state -> Disconnected
279 throw BTException("SMPHandler::send: l2cap write: Error: Message size has "+std::to_string(len)+" != exp "+std::to_string(msg.pdu.size())
280 +": "+msg.toString()+" -> disconnect: "+deviceString, E_FILE_LINE);
281 }
282 } else {
283 WORDY_PRINT("SMPHandler::reader: l2cap read: IRQed res %d (%s); %s",
284 len, L2CAPClient::getRWExitCodeString(len).c_str(), getStateString().c_str());
285 }
286}
287
288std::unique_ptr<const SMPPDUMsg> SMPHandler::sendWithReply(const SMPPDUMsg & msg, const jau::fraction_i64& timeout) {
289 send( msg );
290
291 // Ringbuffer read is thread safe
292 std::unique_ptr<const SMPPDUMsg> res;
293 if( !smpPDURing.getBlocking(res, timeout) || nullptr == res ) {
294 errno = ETIMEDOUT;
295 IRQ_PRINT("SMPHandler::sendWithReply: nullptr result (timeout %d): req %s to %s", timeout, msg.toString().c_str(), deviceString.c_str());
296 has_ioerror = true;
297 disconnect(true /* disconnectDevice */, true /* ioErrorCause */);
298 throw BTException("SMPHandler::sendWithReply: nullptr result (timeout "+timeout.to_string()+"): req "+msg.toString()+" to "+deviceString, E_FILE_LINE);
299 }
300 return res;
301}
302
303/**
304 * SMPSecurityReqCallback handling
305 */
306
308 [](const SMPHandler::SMPSecurityReqCallback& a, const SMPHandler::SMPSecurityReqCallback& b) -> bool { return a == b; };
309
310
312 smpSecurityReqCallbackList.push_back(l);
313}
315 return smpSecurityReqCallbackList.erase_matching(l, true /* all_matching */, _changedSMPSecurityReqCallbackEqComp);
316}
317
318void SMPHandler::clearAllCallbacks() noexcept {
319 smpSecurityReqCallbackList.clear();
320}
321
static SMPHandler::SMPSecurityReqCallbackList::equal_comparator _changedSMPSecurityReqCallbackEqComp
SMPSecurityReqCallback handling.
Definition: SMPHandler.cpp:307
#define E_FILE_LINE
HCIStatusCode disconnect(const HCIStatusCode reason=HCIStatusCode::REMOTE_USER_TERMINATED_CONNECTION) noexcept
Disconnect the LE or BREDR peer's GATT and HCI connection.
Definition: BTDevice.cpp:2488
static std::string getRWExitCodeString(const RWExitCode ec) noexcept
Definition: L2CAPComm.cpp:510
std::string getStateString() const noexcept override
Definition: L2CAPComm.hpp:271
jau::snsize_t write(const uint8_t *buffer, const jau::nsize_t length) noexcept
Generic write, locking mutex_write().
Definition: L2CAPComm.cpp:643
static constexpr int number(const Defaults d) noexcept
Definition: L2CAPComm.hpp:200
bool hasIOError() const noexcept
Definition: L2CAPComm.hpp:270
bool is_open() const noexcept
Definition: L2CAPComm.hpp:173
static SMPEnv & get() noexcept
Definition: SMPHandler.hpp:133
void addSMPSecurityReqCallback(const SMPSecurityReqCallback &l)
Definition: SMPHandler.cpp:311
static bool IS_SUPPORTED_BY_OS
Linux/BlueZ prohibits access to the existing SMP implementation via L2CAP (socket).
Definition: SMPHandler.hpp:168
SMPHandler(const std::shared_ptr< BTDevice > &device) noexcept
Definition: SMPHandler.cpp:169
size_type removeSMPSecurityReqCallback(const SMPSecurityReqCallback &l)
Definition: SMPHandler.cpp:314
std::shared_ptr< BTDevice > getDeviceChecked() const
Definition: SMPHandler.cpp:73
~SMPHandler() noexcept
Destructor closing this instance including L2CAP channel, see disconnect().
Definition: SMPHandler.cpp:199
jau::nsize_t size_type
Definition: SMPHandler.hpp:182
bool establishSecurity(const BTSecurityLevel sec_level)
If sec_level > BTSecurityLevel::UNSET, change security level per L2CAP connection.
Definition: SMPHandler.cpp:204
std::string getStateString() const noexcept
Definition: SMPHandler.hpp:230
bool disconnect(const bool disconnectDevice, const bool ioErrorCause) noexcept
Disconnect this GATTHandler and optionally the associated device.
Definition: SMPHandler.cpp:211
Handles the Security Manager Protocol (SMP) using Protocol Data Unit (PDU) encoded messages over L2CA...
Definition: SMPTypes.hpp:842
jau::POctets pdu
actual received PDU
Definition: SMPTypes.hpp:905
static std::unique_ptr< const SMPPDUMsg > getSpecialized(const uint8_t *buffer, jau::nsize_t const buffer_size) noexcept
Return a newly created specialized instance pointer to base class.
Definition: SMPTypes.cpp:426
virtual std::string toString() const noexcept
Definition: SMPTypes.hpp:1004
Opcode
SMP Command Codes Vol 3, Part H (SM): 3.3.
Definition: SMPTypes.hpp:845
constexpr nsize_t size() const noexcept
Returns the used memory size for read and write operations, may be zero.
Definition: octets.hpp:162
constexpr uint8_t const * get_ptr() const noexcept
Definition: octets.hpp:272
constexpr_atomic void push_back(const value_type &x)
Like std::vector::push_back(), copy.
Definition: cow_darray.hpp:811
constexpr_atomic void clear() noexcept
Like std::vector::clear(), but ending with zero capacity.
Definition: cow_darray.hpp:752
bool(* equal_comparator)(const value_type &a, const value_type &b)
Generic value_type equal comparator to be user defined for e.g.
Definition: cow_darray.hpp:989
constexpr_atomic size_type erase_matching(const value_type &x, const bool all_matching, equal_comparator comparator)
Erase either the first matching element or all matching elements.
Main jau environment class, supporting environment variable access and fetching elapsed time using it...
Definition: environment.hpp:74
Service runner, a reusable dedicated thread performing custom user services.
bool shall_stop2(int dummy) noexcept
Helper function to easy FunctionDef usage w/o creating a lambda alike capture with same semantics as ...
#define ERR_PRINT(...)
Use for unconditional error messages, prefix '[elapsed_time] Error @ FILE:LINE FUNC: '.
Definition: debug.hpp:109
#define PERF3_TS_TD(m)
Definition: debug.hpp:94
#define COND_PRINT(C,...)
Use for conditional plain messages, prefix '[elapsed_time] '.
Definition: debug.hpp:151
#define WORDY_PRINT(...)
Use for environment-variable environment::VERBOSE conditional verbose messages, prefix '[elapsed_time...
Definition: debug.hpp:68
#define DBG_PRINT(...)
Use for environment-variable environment::DEBUG conditional debug messages, prefix '[elapsed_time] De...
Definition: debug.hpp:52
#define PERF3_TS_T0()
Definition: debug.hpp:93
#define ERR_PRINT2(...)
Use for unconditional error messages, prefix '[elapsed_time] Error @ FILE:LINE FUNC: '.
Definition: debug.hpp:112
#define WARN_PRINT(...)
Use for unconditional warning messages, prefix '[elapsed_time] Warning @ FILE:LINE FUNC: '.
Definition: debug.hpp:123
#define IRQ_PRINT(...)
Use for unconditional interruption messages, prefix '[elapsed_time] Interrupted @ FILE:LINE FUNC: '.
Definition: debug.hpp:115
constexpr UnaryFunction for_each_fidelity(InputIt first, InputIt last, UnaryFunction f)
Like jau::for_each(), see above.
@ little
Identifier for little endian, equivalent to endian::little.
std::string to_string(const alphabet &v) noexcept
Definition: base_codec.hpp:97
constexpr const bool SMP_SUPPORTED_BY_OS
Definition: DBTConst.hpp:58
constexpr const jau::fraction_i64 THREAD_SHUTDOWN_TIMEOUT_MS
Maximum time in fractions of seconds to wait for a thread shutdown.
Definition: DBTConst.hpp:73
std::shared_ptr< BTDevice > BTDeviceRef
Definition: BTDevice.hpp:1347
BTSecurityLevel
Bluetooth Security Level.
Definition: BTTypes0.hpp:267
HCIStatusCode
BT Core Spec v5.2: Vol 1, Part F Controller Error Codes: 1.3 List of Error Codes.
Definition: HCITypes.hpp:138
constexpr uint8_t number(const DiscoveryPolicy rhs) noexcept
Definition: BTAdapter.hpp:100
jau::function< R(A...)> bind_member(C1 *base, R(C0::*mfunc)(A...)) noexcept
Bind given class instance and non-void member function to an anonymous function using func_member_tar...
@ timeout
Input or output operation failed due to timeout.
constexpr T min(const T x, const T y) noexcept
Returns the minimum of two integrals (w/ branching) in O(1)
Definition: base_math.hpp:177
uint_fast32_t nsize_t
Natural 'size_t' alternative using uint_fast32_t as its natural sized type.
Definition: int_types.hpp:53
int_fast32_t snsize_t
Natural 'ssize_t' alternative using int_fast32_t as its natural sized type.
Definition: int_types.hpp:65
__pack(...): Produces MSVC, clang and gcc compatible lead-in and -out macros.
Definition: backtrace.hpp:32