summaryrefslogtreecommitdiffstats
path: root/api/direct_bt/L2CAPComm.hpp
blob: 830bbc0c7a0ec7bde018ef82876f97a95895988d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
/*
 * Author: Sven Gothel <sgothel@jausoft.com>
 * Copyright (c) 2020 Gothel Software e.K.
 * Copyright (c) 2020 ZAFENA AB
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef L2CAP_COMM_HPP_
#define L2CAP_COMM_HPP_

#include <cstring>
#include <string>
#include <memory>
#include <cstdint>

#include <mutex>
#include <atomic>

#include <jau/environment.hpp>
#include <jau/uuid.hpp>
#include <jau/function_def.hpp>

#include "BTTypes0.hpp"

extern "C" {
    #include <pthread.h>
}

/**
 * - - - - - - - - - - - - - - -
 *
 * Module L2CAPComm:
 *
 * - BT Core Spec v5.2: Vol 3, Part A: BT Logical Link Control and Adaption Protocol (L2CAP)
 */
namespace direct_bt {

    class BTDevice; // forward

    /** \addtogroup DBTSystemAPI
     *
     *  @{
     */

    /**
     * L2CAP Singleton runtime environment properties
     * <p>
     * Also see {@link DBTEnv::getExplodingProperties(const std::string & prefixDomain)}.
     * </p>
     */
    class L2CAPEnv : public jau::root_environment {
        private:
            L2CAPEnv() noexcept;

            const bool exploding; // just to trigger exploding properties

        public:
            /**
             * L2CAP poll timeout for reading, defaults to 10s.
             * <p>
             * Environment variable is 'direct_bt.l2cap.reader.timeout'.
             * </p>
             */
            const int32_t L2CAP_READER_POLL_TIMEOUT;

            /**
             * Debugging facility: L2CAP restart count on transmission errors, defaults to 5 attempts.
             * <p>
             * If negative, L2CAPComm will abort() the program.
             * </p>
             * <p>
             * Environment variable is 'direct_bt.l2cap.restart.count'.
             * </p>
             */
            const int32_t L2CAP_RESTART_COUNT_ON_ERROR;

            /**
             * Debug all GATT Data communication
             * <p>
             * Environment variable is 'direct_bt.debug.l2cap.data'.
             * </p>
             */
            const bool DEBUG_DATA;

        public:
            static L2CAPEnv& get() noexcept {
                /**
                 * Thread safe starting with C++11 6.7:
                 *
                 * If control enters the declaration concurrently while the variable is being initialized,
                 * the concurrent execution shall wait for completion of the initialization.
                 *
                 * (Magic Statics)
                 *
                 * Avoiding non-working double checked locking.
                 */
                static L2CAPEnv e;
                return e;
            }
    };

    /**
     * L2CAP client/server socket abstract base class to listen for connecting remote devices
     */
    class L2CAPComm {
        public:
            static std::string getStateString(bool isOpen, bool hasIOError) noexcept;
            static std::string getStateString(bool isOpen, bool isInterrupted, bool hasIOError) noexcept;
            static std::string getStateString(bool isOpen, bool irqed_int, bool irqed_ext, bool hasIOError) noexcept;

            /** Utilized to query for external interruption, whether device is still connected etc. */
            typedef jau::FunctionDef<bool, int /* dummy*/> get_boolean_callback_t;

        protected:
            static int l2cap_open_dev(const BDAddressAndType & adapterAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid) noexcept;
            static int l2cap_close_dev(int dd) noexcept;

            const L2CAPEnv & env;

        public:
            /** Corresponding BTAdapter device id */
            const uint16_t adev_id;
            /** Corresponding BTAdapter local BTAddressAndType */
            const BDAddressAndType localAddressAndType;
            /** Corresponding L2CAP_PSM for the channel. */
            const L2CAP_PSM psm;
            /** Corresponding L2CAP_CID for the channel. */
            const L2CAP_CID cid;

        protected:
            std::recursive_mutex mtx_open;
            jau::relaxed_atomic_int socket_; // the native socket
            jau::sc_atomic_bool is_open_; // reflects state
            jau::sc_atomic_bool interrupted_intern; // for forced disconnect and read/accept interruption via close()
            get_boolean_callback_t is_interrupted_extern; // for forced disconnect and read/accept interruption via external event

            bool setBTSecurityLevelImpl(const BTSecurityLevel sec_level, const BDAddressAndType& remoteAddressAndType) noexcept;
            BTSecurityLevel getBTSecurityLevelImpl(const BDAddressAndType& remoteAddressAndType) noexcept;

            /** Returns true if interrupted by internal cause. */
            bool interrupted_int() const noexcept { return interrupted_intern; }

            /** Returns true if interrupted by external cause. */
            bool interrupted_ext() const noexcept { return !is_interrupted_extern.isNullType() && is_interrupted_extern(0/*dummy*/); }

        public:
            L2CAPComm(const uint16_t adev_id, const BDAddressAndType& localAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid) noexcept;

            /** Destructor specialization shall close the L2CAP socket, see {@link #close()}. */
            virtual ~L2CAPComm() noexcept {}

            L2CAPComm(const L2CAPComm&) = delete;
            void operator=(const L2CAPComm&) = delete;

            bool is_open() const noexcept { return is_open_; }

            /** The external `is interrupted` callback is used until close(), thereafter it is removed. */
            void set_interrupted_query(get_boolean_callback_t is_interrupted_cb) noexcept { is_interrupted_extern = is_interrupted_cb; }

            /** Returns true if interrupted by internal or external cause, hence shall stop connecting and reading. */
            bool interrupted() const noexcept { return interrupted_int() || interrupted_ext(); }

            /** Closing the L2CAP socket, see specializations. */
            virtual bool close() noexcept = 0;

            /** Return this L2CAP socket descriptor. */
            inline int socket() const noexcept { return socket_; }

            virtual std::string getStateString() const noexcept = 0;

            virtual std::string toString() const noexcept = 0;
    };

    /**
     * L2CAP read/write communication channel to remote device
     */
    class L2CAPClient : public L2CAPComm {
        public:
            enum class Defaults : int {
                L2CAP_CONNECT_MAX_RETRY = 3
            };
            static constexpr int number(const Defaults d) noexcept { return static_cast<int>(d); }

            /**
             * Exit code for read() and write() operations
             */
            enum class RWExitCode : jau::snsize_t {
                SUCCESS             =  0, /**< SUCCESS */
                NOT_OPEN            = -1, /**< NOT_OPEN */
                INTERRUPTED         = -2, /**< INTERRUPTED */
                INVALID_SOCKET_DD   = -3, /**< INVALID_SOCKET_DD */
                POLL_ERROR          = -10,/**< POLL_ERROR */
                POLL_TIMEOUT        = -11,/**< POLL_TIMEOUT */
                READ_ERROR          = -20,/**< READ_ERROR */
                READ_TIMEOUT        = -21,/**< READ_TIMEOUT */
                WRITE_ERROR         = -30 /**< WRITE_ERROR */
            };
            static constexpr jau::snsize_t number(const RWExitCode rhs) noexcept {
                return static_cast<jau::snsize_t>(rhs);
            }
            static constexpr RWExitCode toRWExitCode(const jau::snsize_t rhs) noexcept {
                return rhs >= 0 ? RWExitCode::SUCCESS : static_cast<RWExitCode>(rhs);
            }
            static std::string getRWExitCodeString(const RWExitCode ec) noexcept;
            static std::string getRWExitCodeString(const jau::snsize_t ecn) noexcept {
                return getRWExitCodeString( toRWExitCode( ecn ) );
            }

        private:
            std::recursive_mutex mtx_write;
            BDAddressAndType remoteAddressAndType;
            std::atomic<bool> has_ioerror;  // reflects state
            std::atomic<::pthread_t> tid_connect;
            std::atomic<::pthread_t> tid_read;

        public:
            /**
             * Constructing a non connected L2CAP channel instance for the pre-defined PSM and CID.
             */
            L2CAPClient(const uint16_t adev_id, const BDAddressAndType& adapterAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid) noexcept;

            /**
             * Constructing a connected L2CAP channel instance for the pre-defined PSM and CID.
             */
            L2CAPClient(const uint16_t adev_id, const BDAddressAndType& adapterAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid,
                        const BDAddressAndType& remoteAddressAndType, int client_socket) noexcept;

            /** Destructor closing the L2CAP channel, see {@link #close()}. */
            ~L2CAPClient() noexcept { close(); }

            /**
             * Opens and connects the L2CAP channel, locking {@link #mutex_write()}.
             * <p>
             * BT Core Spec v5.2: Vol 3, Part A: L2CAP_CONNECTION_REQ
             * </p>
             *
             * @param device the remote device to establish this L2CAP connection
             * @param sec_level sec_level < BTSecurityLevel::NONE will not set security level
             * @return true if connection has been established, otherwise false
             */
            bool open(const BTDevice& device, const BTSecurityLevel sec_level=BTSecurityLevel::NONE) noexcept;

            const BDAddressAndType& getRemoteAddressAndType() const noexcept { return remoteAddressAndType; }

            /** Closing the L2CAP channel, locking {@link #mutex_write()}. */
            bool close() noexcept override;

            bool hasIOError() const noexcept { return has_ioerror; }
            std::string getStateString() const noexcept override { return L2CAPComm::getStateString(is_open_, interrupted_int(), interrupted_ext(), has_ioerror); }

            /** Return the recursive write mutex for multithreading access. */
            std::recursive_mutex & mutex_write() noexcept { return mtx_write; }

            /**
             * If sec_level > ::BTSecurityLevel::UNSET, sets the BlueZ's L2CAP socket BT_SECURITY sec_level, determining the SMP security mode per connection.
             *
             * To unset security, the L2CAP socket should be closed and opened again.
             *
             * If setting the security level fails, close() will be called.
             *
             * @param sec_level sec_level == ::BTSecurityLevel::UNSET will not set security level and returns true.
             * @return true if a security level > ::BTSecurityLevel::UNSET has been set successfully, false if no security level has been set or if it failed.
             */
            bool setBTSecurityLevel(const BTSecurityLevel sec_level) noexcept;

            /**
             * Fetches the current BlueZ's L2CAP socket BT_SECURITY sec_level.
             *
             * @return ::BTSecurityLevel  sec_level value, ::BTSecurityLevel::UNSET if failure
             */
            BTSecurityLevel getBTSecurityLevel() noexcept;

            /**
             * Generic read, w/o locking suitable for a unique ringbuffer sink. Using L2CAPEnv::L2CAP_READER_POLL_TIMEOUT.
             * @param buffer
             * @param capacity
             * @return number of bytes read if >= 0, otherwise L2CAPComm::ExitCode error code.
             */
            jau::snsize_t read(uint8_t* buffer, const jau::nsize_t capacity) noexcept;

            /**
             * Generic write, locking {@link #mutex_write()}.
             * @param buffer
             * @param length
             * @return number of bytes written if >= 0, otherwise L2CAPComm::ExitCode error code.
             */
            jau::snsize_t write(const uint8_t *buffer, const jau::nsize_t length) noexcept;

            std::string toString() const noexcept override;
    };

    /**
     * L2CAP server socket to listen for connecting remote devices
     */
    class L2CAPServer : public L2CAPComm {
        private:
            std::atomic<::pthread_t> tid_accept;

        public:
            L2CAPServer(const uint16_t adev_id, const BDAddressAndType& localAddressAndType, const L2CAP_PSM psm, const L2CAP_CID cid) noexcept;

            /** Destructor closing the L2CAP channel, see {@link #close()}. */
            ~L2CAPServer() noexcept { close(); }

            bool open() noexcept;

            bool close() noexcept override;

            std::unique_ptr<L2CAPClient> accept() noexcept;

            std::string getStateString() const noexcept override { return L2CAPComm::getStateString(is_open_, interrupted_int(), interrupted_ext(), false /* has_ioerror */); }

            std::string toString() const noexcept override;
    };

    /**@}*/

} // namespace direct_bt

#endif /* L2CAP_COMM_HPP_ */