summaryrefslogtreecommitdiffstats
path: root/api/direct_bt/L2CAPComm.hpp
blob: 496287b68e7aec2578a838fb2948b6b2cc2b7db0 (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
/*
 * 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 <vector>

#include <mutex>
#include <atomic>

#include <jau/environment.hpp>

#include "UUID.hpp"
#include "BTTypes.hpp"

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

    class DBTDevice; // forward

    /**
     * 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;
            }
    };

    /**
     * Read/Write L2CAP communication channel.
     */
    class L2CAPComm {
        public:
            enum class Defaults : int {
                L2CAP_CONNECT_MAX_RETRY = 3
            };
            static constexpr int number(const Defaults d) { return static_cast<int>(d); }

            static std::string getStateString(bool isConnected, bool hasIOError) {
                return "State[connected "+std::to_string(isConnected)+", ioError "+std::to_string(hasIOError)+"]";
            }

        private:
            static int l2cap_open_dev(const EUI48 & adapterAddress, const uint16_t psm, const uint16_t cid, const bool pubaddr);
            static int l2cap_close_dev(int dd);

            const L2CAPEnv & env;

            std::recursive_mutex mtx_write;
            std::shared_ptr<DBTDevice> device;
            const std::string deviceString;
            const uint16_t psm;
            const uint16_t cid;
            std::atomic<int> socket_descriptor; // the l2cap socket
            std::atomic<bool> is_connected; // reflects state
            std::atomic<bool> has_ioerror;  // reflects state
            std::atomic<bool> interrupt_flag; // for forced disconnect
            std::atomic<pthread_t> tid_connect;
            std::atomic<pthread_t> tid_read;

        public:
            /**
             * Constructing a newly opened and connected L2CAP channel instance.
             * <p>
             * BT Core Spec v5.2: Vol 3, Part A: L2CAP_CONNECTION_REQ
             * </p>
             */
            L2CAPComm(std::shared_ptr<DBTDevice> device, const uint16_t psm, const uint16_t cid);

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

            std::shared_ptr<DBTDevice> getDevice() { return device; }

            bool isConnected() const { return is_connected; }
            bool hasIOError() const { return has_ioerror; }
            std::string getStateString() const { return getStateString(is_connected, has_ioerror); }

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

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

            /** Generic read, w/o locking suitable for a unique ringbuffer sink. Using L2CAPEnv::L2CAP_READER_POLL_TIMEOUT.*/
            jau::snsize_t read(uint8_t* buffer, const jau::nsize_t capacity);

            /** Generic write, locking {@link #mutex_write()}. */
            jau::snsize_t write(const uint8_t *buffer, const jau::nsize_t length);
    };

} // namespace direct_bt

#endif /* L2CAP_COMM_HPP_ */