Direct-BT v3.3.0-1-gc2d430c
Direct-BT - Direct Bluetooth Programming.
dbt_client_server1x.hpp
Go to the documentation of this file.
1/**
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2022 Gothel Software e.K.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25#include <iostream>
26#include <cassert>
27#include <cinttypes>
28#include <cstring>
29
31
32#include "dbt_server01.hpp"
33#include "dbt_client01.hpp"
34
35using namespace direct_bt;
36
37// Singleton test framework, alive until test program ends
39
40/**
41 * Testing a full Bluetooth server and client lifecycle of operations, requiring two BT adapter:
42 * - start server advertising
43 * - start client discovery and connect to server when discovered
44 * - client/server processing of connection when ready
45 * - client disconnect
46 * - server stop advertising
47 * - security-level: NONE, ENC_ONLY freshly-paired and ENC_ONLY pre-paired
48 * - reuse server-adapter for client-mode discovery (just toggle on/off)
49 */
51 private:
52 // timeout check: timeout_value < test_duration + timeout_preempt_diff; // let's timeout here before our timeout timer
53 static constexpr const fraction_i64 timeout_preempt_diff = 500_ms;
54
55 std::mutex mtx_sync;
56 BTDeviceRef lastCompletedDevice = nullptr;
57 PairingMode lastCompletedDevicePairingMode = PairingMode::NONE;
58 BTSecurityLevel lastCompletedDeviceSecurityLevel = BTSecurityLevel::NONE;
59 EInfoReport lastCompletedDeviceEIR;
60 int client_power_down_count = 0;
61 int client_power_up_count = 0;
62 bool client_reset_at_ready = false;
63 bool server_reset_at_ready = false;
64 bool client_reset_test = false;
65 bool server_reset_test = false;
66
67 public:
68 void set_client_reset_at_ready(const bool v) noexcept { client_reset_at_ready = v; client_reset_test = v; }
69 void set_server_reset_at_ready(const bool v) noexcept { server_reset_at_ready = v; server_reset_test = v; }
70
71 void test8x_fullCycle(const std::string& suffix, const int protocolSessionCount, const bool server_client_order,
72 const bool serverSC, const BTSecurityLevel secLevelServer, const ExpectedPairing serverExpPairing,
73 const BTSecurityLevel secLevelClient, const ExpectedPairing clientExpPairing)
74 {
75 std::shared_ptr<DBTServer01> server = std::make_shared<DBTServer01>("S-"+suffix, EUI48::ALL_DEVICE, BTMode::DUAL,
76 serverSC, secLevelServer);
77 std::shared_ptr<DBTClient01> client = std::make_shared<DBTClient01>("C-"+suffix, EUI48::ALL_DEVICE, BTMode::DUAL);
78
79 server->setProtocolSessionsLeft( protocolSessionCount );
80
81 client->setProtocolSessionsLeft( protocolSessionCount );
82 client->setDisconnectDevice( true ); // default, auto-disconnect after work is done
83 client->setRemoveDevice( false ); // default, test side-effects
84 client->setDiscoveryPolicy( DiscoveryPolicy::PAUSE_CONNECTED_UNTIL_DISCONNECTED );
85
86 test8x_fullCycle(suffix,
87 DBTConstants::max_connections_per_session, true /* expSuccess */,
88 server_client_order,
89 server, secLevelServer, serverExpPairing,
90 client, secLevelClient, clientExpPairing);
91 }
92
93 void test8x_fullCycle(const std::string& suffix,
94 const int max_connections_per_session, const bool expSuccess,
95 const bool server_client_order,
96 const std::shared_ptr<DBTServerTest>& server, const BTSecurityLevel secLevelServer, const ExpectedPairing serverExpPairing,
97 const std::shared_ptr<DBTClientTest>& client, const BTSecurityLevel secLevelClient, const ExpectedPairing clientExpPairing)
98 {
99 (void)secLevelServer;
100 (void)serverExpPairing;
101
102 const int protocolSessionCount = std::min(server->getProtocolSessionsLeft(), client->getProtocolSessionsLeft());
104
105 std::shared_ptr<BTManager> manager = BTManager::get();
106 {
107 jau::darray<BTAdapterRef> adapters = manager->getAdapters();
108 jau::fprintf_td(stderr, "Adapter: Count %u\n", adapters.size());
109
110 for(jau::nsize_t i=0; i<adapters.size(); i++) {
111 jau::fprintf_td(stderr, "%u: %s\n", i, adapters[i]->toString().c_str());
112 }
113 REQUIRE( adapters.size() >= 2 );
114 }
115
117 server_client_order ? std::vector<DBTEndpointRef>{ server, client } : std::vector<DBTEndpointRef>{ client, server } );
118
119 const std::string serverName = server->getName();
121 {
123 sec->sec_level = secLevelClient;
124 }
125
126 {
127 const std::lock_guard<std::mutex> lock(mtx_sync); // RAII-style acquire and relinquish via destructor
128 lastCompletedDevice = nullptr;
129 lastCompletedDevicePairingMode = PairingMode::NONE;
130 lastCompletedDeviceSecurityLevel = BTSecurityLevel::NONE;
131 lastCompletedDeviceEIR.clear();
132 }
134 private:
135 DBTClientServer1x& parent;
136 public:
138
139 void adapterSettingsChanged(BTAdapter &a, const AdapterSetting oldmask, const AdapterSetting newmask,
140 const AdapterSetting changedmask, const uint64_t timestamp) override {
141 const bool initialSetting = AdapterSetting::NONE == oldmask;
142 if( !initialSetting && isAdapterSettingBitSet(changedmask, AdapterSetting::POWERED) ) {
143 const std::lock_guard<std::mutex> lock(parent.mtx_sync); // RAII-style acquire and relinquish via destructor
144 if( isAdapterSettingBitSet(newmask, AdapterSetting::POWERED) ) {
145 ++parent.client_power_up_count;
146 } else {
147 ++parent.client_power_down_count;
148 }
149 }
150 (void)timestamp;
151 (void)a;
152 }
153
154 void deviceReady(const BTDeviceRef& device, const uint64_t timestamp) override {
155 (void)timestamp;
156 const std::lock_guard<std::mutex> lock(parent.mtx_sync); // RAII-style acquire and relinquish via destructor
157 parent.lastCompletedDevice = device;
158 parent.lastCompletedDevicePairingMode = device->getPairingMode();
159 parent.lastCompletedDeviceSecurityLevel = device->getConnSecurityLevel();
160 parent.lastCompletedDeviceEIR = *device->getEIR();
161 fprintf_td(stderr, "XXXXXX Client Ready: %s\n", device->toString(true).c_str());
162 if( parent.client_reset_at_ready ) {
163 parent.client_reset_at_ready = false;
164 fprintf_td(stderr, "XXXXXX Client Reset.0: %s\n", device->toString(true).c_str());
165 const HCIStatusCode rr = device->getAdapter().reset();
166 fprintf_td(stderr, "XXXXXX Client Reset.X: %s: %s\n", direct_bt::to_string(rr).c_str(), device->toString(true).c_str());
167 }
168 }
169
170 std::string toString() const noexcept override { return "DBTClientServer1x::Client"; }
171 };
172 std::shared_ptr<AdapterStatusListener> clientAdapterStatusListener = std::make_shared<MyAdapterStatusListener>(*this);
173 REQUIRE( true == client->getAdapter()->addStatusListener(clientAdapterStatusListener) );
174
175 //
176 // Server start
177 //
179 DBTServerTest::startAdvertising(server, false /* current_exp_advertising_state */, "test"+suffix+"_startAdvertising");
180
181 //
182 // Client start
183 //
185 DBTClientTest::startDiscovery(client, false /* current_exp_discovering_state */, "test"+suffix+"_startDiscovery");
186
188 const jau::fraction_i64 timeout_value = framework.get_timeout_value();
190 bool done = false;
191 bool timeout = false;
192 bool max_connections_hit = false;
193 do {
194 {
195 const std::lock_guard<std::mutex> lock(mtx_sync); // RAII-style acquire and relinquish via destructor
196 done = ! ( protocolSessionCount > server->getProtocolSessionsDoneSuccess() ||
197 protocolSessionCount > client->getProtocolSessionsDoneSuccess() ||
198 nullptr == lastCompletedDevice ||
199 lastCompletedDevice->getConnected() );
200 }
201 max_connections_hit = ( protocolSessionCount * max_connections_per_session ) <= server->getDisconnectCount();
203 timeout = framework.is_timedout() ||
204 ( 0_s < timeout_value && timeout_value <= test_duration + timeout_preempt_diff ); // let's timeout here before our timeout timer
205 if( !done && !max_connections_hit && !timeout ) {
206 jau::sleep_for( 88_ms );
207 }
208 } while( !done && !max_connections_hit && !timeout );
210
211 fprintf_td(stderr, "\n\n");
212 fprintf_td(stderr, "****** Test Stats: duration %" PRIi64 " ms, timeout[hit %d, value %s sec], max_connections hit %d\n",
213 test_duration.to_ms(), timeout, timeout_value.to_string(true).c_str(), max_connections_hit);
214 fprintf_td(stderr, " Server ProtocolSessions[success %d/%d total, requested %d], disconnects %d of %d max\n",
215 server->getProtocolSessionsDoneSuccess(), server->getProtocolSessionsDoneTotal(), protocolSessionCount,
216 server->getDisconnectCount(), ( protocolSessionCount * max_connections_per_session ));
217 fprintf_td(stderr, " Client ProtocolSessions[success %d/%d total, requested %d], disconnects %d of %d max, power[down %d, up %d]\n",
218 client->getProtocolSessionsDoneSuccess(), client->getProtocolSessionsDoneTotal(), protocolSessionCount,
219 client->getDisconnectCount(), ( protocolSessionCount * max_connections_per_session ),
220 client_power_down_count, client_power_up_count);
221 fprintf_td(stderr, "\n\n");
222
223 if( expSuccess ) {
224 REQUIRE( false == max_connections_hit );
225 REQUIRE( false == timeout );
226 {
227 const std::lock_guard<std::mutex> lock(mtx_sync); // RAII-style acquire and relinquish via destructor
228 REQUIRE( protocolSessionCount <= server->getProtocolSessionsDoneTotal() );
229 REQUIRE( protocolSessionCount == server->getProtocolSessionsDoneSuccess() );
230 REQUIRE( protocolSessionCount <= client->getProtocolSessionsDoneTotal() );
231 REQUIRE( protocolSessionCount == client->getProtocolSessionsDoneSuccess() );
232 REQUIRE( nullptr != lastCompletedDevice );
233 REQUIRE( EIRDataType::NONE != lastCompletedDeviceEIR.getEIRDataMask() );
234 REQUIRE( false == lastCompletedDevice->getConnected() );
235 REQUIRE( ( protocolSessionCount * max_connections_per_session ) > server->getDisconnectCount() );
236 if( client_reset_test ) {
237 REQUIRE( 1 == client_power_down_count );
238 REQUIRE( 1 == client_power_up_count );
239 } else {
240 REQUIRE( 0 == client_power_down_count );
241 REQUIRE( 0 == client_power_up_count );
242 }
243 }
244 }
245
246 //
247 // Client stop
248 //
249 const bool current_exp_discovering_state = expSuccess ? true : client->getAdapter()->isDiscovering();
250 DBTClientTest::stopDiscovery(client, current_exp_discovering_state, "test"+suffix+"_stopDiscovery");
251 client->close("test"+suffix+"_close");
252
253 //
254 // Server stop
255 //
256 DBTServerTest::stop(server, "test"+suffix+"_stop");
257
258 if( expSuccess ) {
259 //
260 // Validating Security Mode
261 //
262 SMPKeyBin clientKeys = SMPKeyBin::read(DBTConstants::CLIENT_KEY_PATH, *lastCompletedDevice, true /* verbose */);
263 REQUIRE( true == clientKeys.isValid() );
264 const BTSecurityLevel clientKeysSecLevel = clientKeys.getSecLevel();
265 REQUIRE( secLevelClient == clientKeysSecLevel);
266 {
267 if( ExpectedPairing::PREPAIRED == clientExpPairing && BTSecurityLevel::NONE < secLevelClient ) {
268 // Using encryption: pre-paired
269 REQUIRE( PairingMode::PRE_PAIRED == lastCompletedDevicePairingMode );
270 REQUIRE( BTSecurityLevel::ENC_ONLY == lastCompletedDeviceSecurityLevel ); // pre-paired fixed level, no auth
271 } else if( ExpectedPairing::NEW_PAIRING == clientExpPairing && BTSecurityLevel::NONE < secLevelClient ) {
272 // Using encryption: Newly paired
273 REQUIRE( PairingMode::PRE_PAIRED != lastCompletedDevicePairingMode );
274 REQUIRE_MSG( "PairingMode client "+to_string(lastCompletedDevicePairingMode)+" not > NONE", PairingMode::NONE < lastCompletedDevicePairingMode );
275 REQUIRE_MSG( "SecurityLevel client "+to_string(lastCompletedDeviceSecurityLevel)+" not >= "+to_string(secLevelClient), secLevelClient <= lastCompletedDeviceSecurityLevel );
276 } else if( ExpectedPairing::DONT_CARE == clientExpPairing && BTSecurityLevel::NONE < secLevelClient ) {
277 // Any encryption, any pairing
278 REQUIRE_MSG( "PairingMode client "+to_string(lastCompletedDevicePairingMode)+" not > NONE", PairingMode::NONE < lastCompletedDevicePairingMode );
279 REQUIRE_MSG( "SecurityLevel client "+to_string(lastCompletedDeviceSecurityLevel)+" not >= "+to_string(secLevelClient), secLevelClient <= lastCompletedDeviceSecurityLevel );
280 } else {
281 // No encryption: No pairing
282 REQUIRE( PairingMode::NONE == lastCompletedDevicePairingMode );
283 REQUIRE( BTSecurityLevel::NONE == lastCompletedDeviceSecurityLevel );
284 }
285 }
286
287 //
288 // Validating EIR
289 //
290 {
291 const std::lock_guard<std::mutex> lock(mtx_sync); // RAII-style acquire and relinquish via destructor
292 fprintf_td(stderr, "lastCompletedDevice.connectedEIR: %s\n", lastCompletedDeviceEIR.toString().c_str());
293 REQUIRE( EIRDataType::NONE != lastCompletedDeviceEIR.getEIRDataMask() );
294 REQUIRE( true == lastCompletedDeviceEIR.isSet(EIRDataType::FLAGS) );
295 REQUIRE( true == lastCompletedDeviceEIR.isSet(EIRDataType::SERVICE_UUID) );
296 REQUIRE( true == lastCompletedDeviceEIR.isSet(EIRDataType::NAME) );
297 REQUIRE( true == lastCompletedDeviceEIR.isSet(EIRDataType::CONN_IVAL) );
298 REQUIRE( serverName == lastCompletedDeviceEIR.getName() );
299 {
300 const EInfoReport eir = *lastCompletedDevice->getEIR();
301 fprintf_td(stderr, "lastCompletedDevice.currentEIR: %s\n", eir.toString().c_str());
302 REQUIRE( EIRDataType::NONE == eir.getEIRDataMask() );
303 REQUIRE( 0 == eir.getName().length());
304 }
305 }
306
307 //
308 // Now reuse adapter for client mode -> Start discovery + Stop Discovery
309 //
310 {
311 const BTAdapterRef adapter = server->getAdapter();
312 { // if( false ) {
313 adapter->removeAllStatusListener();
314 }
315
316 DBTEndpoint::startDiscovery(adapter, false /* current_exp_discovering_state */);
317
318 DBTEndpoint::stopDiscovery(adapter, true /* current_exp_discovering_state */);
319 }
320 }
321 const BTManager::size_type count = manager->removeChangedAdapterSetCallback(myChangedAdapterSetFunc);
322 fprintf_td(stderr, "****** EOL Removed ChangedAdapterSetCallback %zu\n", (size_t)count);
323 }
324};
#define REQUIRE_MSG(MSG,...)
Definition: catch2_ext.hpp:58
bool is_timedout() const noexcept
jau::fraction_i64 get_timeout_value() const
static BaseDBTClientServer & get(const bool btmanager_hold_and_close=true)
Testing a full Bluetooth server and client lifecycle of operations, requiring two BT adapter:
void test8x_fullCycle(const std::string &suffix, const int max_connections_per_session, const bool expSuccess, const bool server_client_order, const std::shared_ptr< DBTServerTest > &server, const BTSecurityLevel secLevelServer, const ExpectedPairing serverExpPairing, const std::shared_ptr< DBTClientTest > &client, const BTSecurityLevel secLevelClient, const ExpectedPairing clientExpPairing)
void set_client_reset_at_ready(const bool v) noexcept
void test8x_fullCycle(const std::string &suffix, const int protocolSessionCount, const bool server_client_order, const bool serverSC, const BTSecurityLevel secLevelServer, const ExpectedPairing serverExpPairing, const BTSecurityLevel secLevelClient, const ExpectedPairing clientExpPairing)
void set_server_reset_at_ready(const bool v) noexcept
virtual HCIStatusCode startDiscovery(const std::string &msg)=0
virtual HCIStatusCode stopDiscovery(const std::string &msg)=0
static constexpr const int max_connections_per_session
static constexpr const char CLIENT_KEY_PATH[]
C++20 we could use constexpr std::string
static void checkInitializedState(const DBTEndpointRef &endp)
static void stopDiscovery(const BTAdapterRef &adapter, const bool current_exp_discovering_state)
static ChangedAdapterSetCallback initChangedAdapterSetListener(const BTManagerRef &manager, std::vector< DBTEndpointRef > endpts)
static void startDiscovery(const BTAdapterRef &adapter, const bool current_exp_discovering_state)
virtual HCIStatusCode startAdvertising(const std::string &msg)=0
static void stop(const DBTServerTestRef &server, const std::string &msg)
BTAdapter status listener for remote BTDevice discovery events: Added, updated and removed; as well a...
Definition: BTAdapter.hpp:127
BTAdapter represents one local Bluetooth Controller.
Definition: BTAdapter.hpp:324
jau::nsize_t size_type
Definition: BTManager.hpp:211
Collection of 'Extended Advertising Data' (EAD), 'Advertising Data' (AD) or 'Extended Inquiry Respons...
Definition: BTTypes0.hpp:898
std::string toString(const bool includeServices=true) const noexcept
Definition: BTTypes0.cpp:854
bool isSet(EIRDataType bit) const noexcept
Definition: BTTypes0.hpp:1110
EIRDataType getEIRDataMask() const noexcept
Definition: BTTypes0.hpp:1111
void clear() noexcept
Reset all data fields.
Definition: BTTypes0.cpp:621
std::string const & getName() const noexcept
Definition: BTTypes0.hpp:1119
Storage for SMP keys including required connection parameter per local adapter and remote device.
Definition: SMPKeyBin.hpp:79
constexpr bool isValid() const noexcept
Returns true if.
Definition: SMPKeyBin.hpp:364
constexpr BTSecurityLevel getSecLevel() const noexcept
Definition: SMPKeyBin.hpp:290
Implementation of a dynamic linear array storage, aka vector.
Definition: darray.hpp:148
constexpr size_type size() const noexcept
Like std::vector::size().
Definition: darray.hpp:763
std::string to_string(const bool show_double=false) const noexcept
Returns a string representation of this fraction.
Class template jau::function is a general-purpose static-polymorphic function wrapper.
static BaseDBTClientServer & base_test_framework
static void myChangedAdapterSetFunc(const bool added, std::shared_ptr< BTAdapter > &adapter)
std::string to_string(const alphabet &v) noexcept
Definition: base_codec.hpp:97
Entry * getOrCreate(const std::string &addrOrNameSub) noexcept
Determines whether the given addrOrNameSub is a EUI48Sub or just a name and retrieves an entry.
std::shared_ptr< BTDevice > BTDeviceRef
Definition: BTDevice.hpp:1347
std::string to_string(const DiscoveryPolicy v) noexcept
Definition: BTAdapter.cpp:58
std::shared_ptr< BTAdapter > BTAdapterRef
Definition: BTAdapter.hpp:1354
Entry * get(const EUI48 &addr, const std::string &name, AddressNameEntryMatchFunc m) noexcept
Returns a matching BTSecurityRegistry::Entry with the given addr and/or name.
AdapterSetting
Adapter Setting Bits.
Definition: BTTypes1.hpp:144
constexpr bool isAdapterSettingBitSet(const AdapterSetting mask, const AdapterSetting bit) noexcept
Definition: BTTypes1.hpp:185
BTSecurityLevel
Bluetooth Security Level.
Definition: BTTypes0.hpp:267
PairingMode
Bluetooth secure pairing mode.
Definition: BTTypes0.hpp:317
HCIStatusCode
BT Core Spec v5.2: Vol 1, Part F Controller Error Codes: 1.3 List of Error Codes.
Definition: HCITypes.hpp:138
bool to_fraction_i64(fraction_i64 &result, const std::string &value, const fraction_i64 &min_allowed, const fraction_i64 &max_allowed) noexcept
Stores the fraction_i64 value of the given string value in format <num>/<denom>, which may contain wh...
fraction_timespec getMonotonicTime() noexcept
Returns current monotonic time since Unix Epoch 00:00:00 UTC on 1970-01-01.
Definition: basic_types.cpp:52
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
void addToWaitForDevices(const std::string &addrOrNameSub) noexcept
int fprintf_td(const uint64_t elapsed_ms, FILE *stream, const char *format,...) noexcept
Convenient fprintf() invocation, prepending the given elapsed_ms timestamp.
Definition: debug.cpp:270
bool sleep_for(const fraction_timespec &relative_time, const bool monotonic=true, const bool ignore_irq=true) noexcept
sleep_for causes the current thread to block until a specific amount of time has passed.
Timespec structure using int64_t for its components in analogy to struct timespec_t on 64-bit platfor...
static void test_duration(const fraction< int_type > &a, const std::chrono::duration< Rep, Period > &dur_ref, const Rep exp_count)
ExpectedPairing