Direct-BT v3.3.0-1-gc2d430c
Direct-BT - Direct Bluetooth Programming.
L2CAPComm.hpp
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#ifndef L2CAP_COMM_HPP_
27#define L2CAP_COMM_HPP_
28
29#include <cstring>
30#include <string>
31#include <memory>
32#include <cstdint>
33
34#include <mutex>
35#include <atomic>
36
37#include <jau/environment.hpp>
38#include <jau/uuid.hpp>
39#include <jau/functional.hpp>
40
41#include "BTTypes0.hpp"
42
43extern "C" {
44 #include <pthread.h>
45}
46
47/**
48 * - - - - - - - - - - - - - - -
49 *
50 * Module L2CAPComm:
51 *
52 * - BT Core Spec v5.2: Vol 3, Part A: BT Logical Link Control and Adaption Protocol (L2CAP)
53 */
54namespace direct_bt {
55
56 class BTDevice; // forward
57
58 /** \addtogroup DBTSystemAPI
59 *
60 * @{
61 */
62
63 /**
64 * L2CAP Singleton runtime environment properties
65 * <p>
66 * Also see {@link DBTEnv::getExplodingProperties(const std::string & prefixDomain)}.
67 * </p>
68 */
70 private:
71 L2CAPEnv() noexcept; // NOLINT(modernize-use-equals-delete)
72
73 const bool exploding; // just to trigger exploding properties
74
75 public:
76 /**
77 * L2CAP poll timeout for reading, defaults to 10s.
78 * <p>
79 * Environment variable is 'direct_bt.l2cap.reader.timeout'.
80 * </p>
81 */
83
84 /**
85 * Debugging facility: L2CAP restart count on transmission errors, defaults to 5 attempts.
86 * <p>
87 * If negative, L2CAPComm will abort() the program.
88 * </p>
89 * <p>
90 * Environment variable is 'direct_bt.l2cap.restart.count'.
91 * </p>
92 */
94
95 /**
96 * Debug all GATT Data communication
97 * <p>
98 * Environment variable is 'direct_bt.debug.l2cap.data'.
99 * </p>
100 */
101 const bool DEBUG_DATA;
102
103 public:
104 static L2CAPEnv& get() noexcept {
105 /**
106 * Thread safe starting with C++11 6.7:
107 *
108 * If control enters the declaration concurrently while the variable is being initialized,
109 * the concurrent execution shall wait for completion of the initialization.
110 *
111 * (Magic Statics)
112 *
113 * Avoiding non-working double checked locking.
114 */
115 static L2CAPEnv e;
116 return e;
117 }
118 };
119
120 /**
121 * L2CAP client/server socket abstract base class to listen for connecting remote devices
122 */
123 class L2CAPComm {
124 public:
125 static std::string getStateString(bool isOpen, bool hasIOError) noexcept;
126 static std::string getStateString(bool isOpen, bool isInterrupted, bool hasIOError) noexcept;
127 static std::string getStateString(bool isOpen, bool irqed_int, bool irqed_ext, bool hasIOError) noexcept;
128
129 /** Utilized to query for external interruption, whether device is still connected etc. */
130 typedef jau::function<bool(int /* dummy*/)> get_boolean_callback_t;
131
132 protected:
133 static int l2cap_open_dev(const BDAddressAndType & adapterAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid) noexcept;
134 static int l2cap_close_dev(int dd) noexcept;
135
136 const L2CAPEnv & env;
137
138 public:
139 /** Corresponding BTAdapter device id */
140 const uint16_t adev_id;
141 /** Corresponding BTAdapter local BTAddressAndType */
143 /** Corresponding L2CAP_PSM for the channel. */
145 /** Corresponding L2CAP_CID for the channel. */
147
148 protected:
149 std::recursive_mutex mtx_open;
150 jau::relaxed_atomic_int socket_; // the native socket
151 jau::sc_atomic_bool is_open_; // reflects state
152 jau::sc_atomic_bool interrupted_intern; // for forced disconnect and read/accept interruption via close()
153 get_boolean_callback_t is_interrupted_extern; // for forced disconnect and read/accept interruption via external event
154
155 bool setBTSecurityLevelImpl(const BTSecurityLevel sec_level, const BDAddressAndType& remoteAddressAndType) noexcept;
156 BTSecurityLevel getBTSecurityLevelImpl(const BDAddressAndType& remoteAddressAndType) noexcept;
157
158 /** Returns true if interrupted by internal cause. */
159 bool interrupted_int() const noexcept { return interrupted_intern; }
160
161 /** Returns true if interrupted by external cause. */
162 bool interrupted_ext() const noexcept { return !is_interrupted_extern.is_null() && is_interrupted_extern(0/*dummy*/); }
163
164 public:
165 L2CAPComm(const uint16_t adev_id, BDAddressAndType localAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid) noexcept;
166
167 /** Destructor specialization shall close the L2CAP socket, see {@link #close()}. */
168 virtual ~L2CAPComm() noexcept = default;
169
170 L2CAPComm(const L2CAPComm&) = delete;
171 void operator=(const L2CAPComm&) = delete;
172
173 bool is_open() const noexcept { return is_open_; }
174
175 /** The external `is interrupted` callback is used until close(), thereafter it is removed. */
176 void set_interrupted_query(get_boolean_callback_t is_interrupted_cb) noexcept { is_interrupted_extern = std::move(is_interrupted_cb); }
177
178 /** Returns true if interrupted by internal or external cause, hence shall stop connecting and reading. */
179 bool interrupted() const noexcept { return interrupted_int() || interrupted_ext(); }
180
181 /** Closing the L2CAP socket, see specializations. */
182 virtual bool close() noexcept = 0;
183
184 /** Return this L2CAP socket descriptor. */
185 inline int socket() const noexcept { return socket_; }
186
187 virtual std::string getStateString() const noexcept = 0;
188
189 virtual std::string toString() const noexcept = 0;
190 };
191
192 /**
193 * L2CAP read/write communication channel to remote device
194 */
195 class L2CAPClient : public L2CAPComm {
196 public:
197 enum class Defaults : int {
198 L2CAP_CONNECT_MAX_RETRY = 3
199 };
200 static constexpr int number(const Defaults d) noexcept { return static_cast<int>(d); }
201
202 /**
203 * Exit code for read() and write() operations
204 */
206 SUCCESS = 0, /**< SUCCESS */
207 NOT_OPEN = -1, /**< NOT_OPEN */
208 INTERRUPTED = -2, /**< INTERRUPTED */
209 INVALID_SOCKET_DD = -3, /**< INVALID_SOCKET_DD */
210 POLL_ERROR = -10,/**< POLL_ERROR */
211 POLL_TIMEOUT = -11,/**< POLL_TIMEOUT */
212 READ_ERROR = -20,/**< READ_ERROR */
213 READ_TIMEOUT = -21,/**< READ_TIMEOUT */
214 WRITE_ERROR = -30 /**< WRITE_ERROR */
215 };
216 static constexpr jau::snsize_t number(const RWExitCode rhs) noexcept {
217 return static_cast<jau::snsize_t>(rhs);
218 }
219 static constexpr RWExitCode toRWExitCode(const jau::snsize_t rhs) noexcept {
220 return rhs >= 0 ? RWExitCode::SUCCESS : static_cast<RWExitCode>(rhs);
221 }
222 static std::string getRWExitCodeString(const RWExitCode ec) noexcept;
223 static std::string getRWExitCodeString(const jau::snsize_t ecn) noexcept {
224 return getRWExitCodeString( toRWExitCode( ecn ) );
225 }
226
227 private:
228 std::recursive_mutex mtx_write;
229 BDAddressAndType remoteAddressAndType;
230 std::atomic<bool> has_ioerror; // reflects state
231 std::atomic<::pthread_t> tid_connect;
232 std::atomic<::pthread_t> tid_read;
233
234 bool close_impl() noexcept;
235
236 public:
237 /**
238 * Constructing a non connected L2CAP channel instance for the pre-defined PSM and CID.
239 */
240 L2CAPClient(const uint16_t adev_id, BDAddressAndType adapterAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid) noexcept;
241
242 /**
243 * Constructing a connected L2CAP channel instance for the pre-defined PSM and CID.
244 */
245 L2CAPClient(const uint16_t adev_id, BDAddressAndType adapterAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid,
246 BDAddressAndType remoteAddressAndType, int client_socket) noexcept;
247
248 /** Destructor closing the L2CAP channel, see {@link #close()}. */
249 ~L2CAPClient() noexcept override {
250 close_impl();
251 }
252
253 /**
254 * Opens and connects the L2CAP channel, locking {@link #mutex_write()}.
255 * <p>
256 * BT Core Spec v5.2: Vol 3, Part A: L2CAP_CONNECTION_REQ
257 * </p>
258 *
259 * @param device the remote device to establish this L2CAP connection
260 * @param sec_level sec_level < BTSecurityLevel::NONE will not set security level
261 * @return true if connection has been established, otherwise false
262 */
263 bool open(const BTDevice& device, const BTSecurityLevel sec_level=BTSecurityLevel::NONE) noexcept;
264
265 const BDAddressAndType& getRemoteAddressAndType() const noexcept { return remoteAddressAndType; }
266
267 /** Closing the L2CAP channel, locking {@link #mutex_write()}. */
268 bool close() noexcept override { return close_impl(); }
269
270 bool hasIOError() const noexcept { return has_ioerror; }
271 std::string getStateString() const noexcept override { return L2CAPComm::getStateString(is_open_, interrupted_int(), interrupted_ext(), has_ioerror); }
272
273 /** Return the recursive write mutex for multithreading access. */
274 std::recursive_mutex & mutex_write() noexcept { return mtx_write; }
275
276 /**
277 * If sec_level > ::BTSecurityLevel::UNSET, sets the BlueZ's L2CAP socket BT_SECURITY sec_level, determining the SMP security mode per connection.
278 *
279 * To unset security, the L2CAP socket should be closed and opened again.
280 *
281 * If setting the security level fails, close() will be called.
282 *
283 * @param sec_level sec_level == ::BTSecurityLevel::UNSET will not set security level and returns true.
284 * @return true if a security level > ::BTSecurityLevel::UNSET has been set successfully, false if no security level has been set or if it failed.
285 */
286 bool setBTSecurityLevel(const BTSecurityLevel sec_level) noexcept;
287
288 /**
289 * Fetches the current BlueZ's L2CAP socket BT_SECURITY sec_level.
290 *
291 * @return ::BTSecurityLevel sec_level value, ::BTSecurityLevel::UNSET if failure
292 */
293 BTSecurityLevel getBTSecurityLevel() noexcept;
294
295 /**
296 * Generic read, w/o locking suitable for a unique ringbuffer sink. Using L2CAPEnv::L2CAP_READER_POLL_TIMEOUT.
297 * @param buffer
298 * @param capacity
299 * @return number of bytes read if >= 0, otherwise L2CAPComm::ExitCode error code.
300 */
301 jau::snsize_t read(uint8_t* buffer, const jau::nsize_t capacity) noexcept;
302
303 /**
304 * Generic write, locking {@link #mutex_write()}.
305 * @param buffer
306 * @param length
307 * @return number of bytes written if >= 0, otherwise L2CAPComm::ExitCode error code.
308 */
309 jau::snsize_t write(const uint8_t *buffer, const jau::nsize_t length) noexcept;
310
311 std::string toString() const noexcept override;
312 };
313
314 /**
315 * L2CAP server socket to listen for connecting remote devices
316 */
317 class L2CAPServer : public L2CAPComm {
318 private:
319 std::atomic<::pthread_t> tid_accept;
320
321 bool close_impl() noexcept;
322
323 public:
324 L2CAPServer(const uint16_t adev_id, BDAddressAndType localAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid) noexcept;
325
326 /** Destructor closing the L2CAP channel, see {@link #close()}. */
327 ~L2CAPServer() noexcept override {
328 close_impl();
329 }
330
331 bool open() noexcept;
332
333 bool close() noexcept override { return close_impl(); }
334
335 std::unique_ptr<L2CAPClient> accept() noexcept;
336
337 std::string getStateString() const noexcept override { return L2CAPComm::getStateString(is_open_, interrupted_int(), interrupted_ext(), false /* has_ioerror */); }
338
339 std::string toString() const noexcept override;
340 };
341
342 /**@}*/
343
344} // namespace direct_bt
345
346#endif /* L2CAP_COMM_HPP_ */
Unique Bluetooth EUI48 address and BDAddressType tuple.
Definition: BTAddress.hpp:175
BTDevice represents one remote Bluetooth device.
Definition: BTDevice.hpp:81
L2CAP read/write communication channel to remote device.
Definition: L2CAPComm.hpp:195
bool close() noexcept override
Closing the L2CAP channel, locking mutex_write().
Definition: L2CAPComm.hpp:268
std::string getStateString() const noexcept override
Definition: L2CAPComm.hpp:271
static std::string getRWExitCodeString(const jau::snsize_t ecn) noexcept
Definition: L2CAPComm.hpp:223
std::recursive_mutex & mutex_write() noexcept
Return the recursive write mutex for multithreading access.
Definition: L2CAPComm.hpp:274
static constexpr int number(const Defaults d) noexcept
Definition: L2CAPComm.hpp:200
static constexpr RWExitCode toRWExitCode(const jau::snsize_t rhs) noexcept
Definition: L2CAPComm.hpp:219
bool hasIOError() const noexcept
Definition: L2CAPComm.hpp:270
RWExitCode
Exit code for read() and write() operations.
Definition: L2CAPComm.hpp:205
static constexpr jau::snsize_t number(const RWExitCode rhs) noexcept
Definition: L2CAPComm.hpp:216
const BDAddressAndType & getRemoteAddressAndType() const noexcept
Definition: L2CAPComm.hpp:265
L2CAP client/server socket abstract base class to listen for connecting remote devices.
Definition: L2CAPComm.hpp:123
jau::sc_atomic_bool interrupted_intern
Definition: L2CAPComm.hpp:152
L2CAPComm(const uint16_t adev_id, BDAddressAndType localAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid) noexcept
Definition: L2CAPComm.cpp:147
BTSecurityLevel getBTSecurityLevelImpl(const BDAddressAndType &remoteAddressAndType) noexcept
Definition: L2CAPComm.cpp:208
const BDAddressAndType localAddressAndType
Corresponding BTAdapter local BTAddressAndType.
Definition: L2CAPComm.hpp:142
int socket() const noexcept
Return this L2CAP socket descriptor.
Definition: L2CAPComm.hpp:185
virtual ~L2CAPComm() noexcept=default
Destructor specialization shall close the L2CAP socket, see close().
void set_interrupted_query(get_boolean_callback_t is_interrupted_cb) noexcept
The external is interrupted callback is used until close(), thereafter it is removed.
Definition: L2CAPComm.hpp:176
bool interrupted_ext() const noexcept
Returns true if interrupted by external cause.
Definition: L2CAPComm.hpp:162
const L2CAP_CID cid
Corresponding L2CAP_CID for the channel.
Definition: L2CAPComm.hpp:146
bool interrupted_int() const noexcept
Returns true if interrupted by internal cause.
Definition: L2CAPComm.hpp:159
jau::relaxed_atomic_int socket_
Definition: L2CAPComm.hpp:150
static int l2cap_close_dev(int dd) noexcept
Definition: L2CAPComm.cpp:142
static int l2cap_open_dev(const BDAddressAndType &adapterAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid) noexcept
Definition: L2CAPComm.cpp:97
virtual bool close() noexcept=0
Closing the L2CAP socket, see specializations.
virtual std::string getStateString() const noexcept=0
bool is_open() const noexcept
Definition: L2CAPComm.hpp:173
get_boolean_callback_t is_interrupted_extern
Definition: L2CAPComm.hpp:153
jau::sc_atomic_bool is_open_
Definition: L2CAPComm.hpp:151
std::recursive_mutex mtx_open
Definition: L2CAPComm.hpp:149
jau::function< bool(int)> get_boolean_callback_t
Utilized to query for external interruption, whether device is still connected etc.
Definition: L2CAPComm.hpp:130
bool interrupted() const noexcept
Returns true if interrupted by internal or external cause, hence shall stop connecting and reading.
Definition: L2CAPComm.hpp:179
const L2CAP_PSM psm
Corresponding L2CAP_PSM for the channel.
Definition: L2CAPComm.hpp:144
const L2CAPEnv & env
Definition: L2CAPComm.hpp:136
const uint16_t adev_id
Corresponding BTAdapter device id.
Definition: L2CAPComm.hpp:140
bool setBTSecurityLevelImpl(const BTSecurityLevel sec_level, const BDAddressAndType &remoteAddressAndType) noexcept
Definition: L2CAPComm.cpp:156
static std::string getStateString(bool isOpen, bool isInterrupted, bool hasIOError) noexcept
virtual std::string toString() const noexcept=0
L2CAP Singleton runtime environment properties.
Definition: L2CAPComm.hpp:69
const int32_t L2CAP_READER_POLL_TIMEOUT
L2CAP poll timeout for reading, defaults to 10s.
Definition: L2CAPComm.hpp:82
const bool DEBUG_DATA
Debug all GATT Data communication.
Definition: L2CAPComm.hpp:101
const int32_t L2CAP_RESTART_COUNT_ON_ERROR
Debugging facility: L2CAP restart count on transmission errors, defaults to 5 attempts.
Definition: L2CAPComm.hpp:93
static L2CAPEnv & get() noexcept
Definition: L2CAPComm.hpp:104
L2CAP server socket to listen for connecting remote devices.
Definition: L2CAPComm.hpp:317
~L2CAPServer() noexcept override
Destructor closing the L2CAP channel, see close().
Definition: L2CAPComm.hpp:327
Class template jau::function is a general-purpose static-polymorphic function wrapper.
Base jau environment class, merely to tag all environment settings by inheritance and hence documenta...
Definition: environment.hpp:57
L2CAP_PSM
Protocol Service Multiplexers (PSM) Assigned numbers https://www.bluetooth.com/specifications/assigne...
Definition: BTTypes0.hpp:514
BTSecurityLevel
Bluetooth Security Level.
Definition: BTTypes0.hpp:267
@ NONE
No encryption and no authentication.
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
STL namespace.