aboutsummaryrefslogtreecommitdiff
path: root/src/Gobbledegook.cpp
blob: 218dffb5bb355a4137b45f2bea7a36dafeda9059 (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
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
// Copyright 2017 Paul Nettle. 
// 
// This file is part of Gobbledegook. 
// 
// Gobbledegook is free software: you can redistribute it and/or modify 
// it under the terms of the GNU General Public License as published by 
// the Free Software Foundation, either version 3 of the License, or 
// (at your option) any later version. 
// 
// Gobbledegook is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
// GNU General Public License for more details. 
// 
// You should have received a copy of the GNU General Public License 
// along with Gobbledegook.  If not, see <http://www.gnu.org/licenses/>. 
 
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
// 
// >> 
// >>>  INSIDE THIS FILE 
// >> 
// 
// The methods in this file represent the complete external C interface for a Gobbledegook server. 
// 
// >> 
// >>>  DISCUSSION 
// >> 
// 
// Although Gobbledegook requires customization (see Server.cpp), it still maintains a public interface (this file.) This keeps the 
// interface to the server simple and compact, while also allowing the server to be built as a library if the developer so chooses. 
// 
// As an alternative, it is also possible to publish customized Gobbledegook servers, allowing others to use the server through the 
// public interface defined in this file. 
// 
// In addition, this interface is compatible with the C language, allowing non-C++ programs to interface with Gobbledegook, even 
// though Gobbledgook is a C++ codebase. This should also simplify the use of this interface with other languages, such as Swift. 
// 
// The interface below has the following categories: 
// 
//     Log registration - used to register methods that accept all Gobbledegook logs 
//     Update queue management - used for notifying the server that data has been updated 
//     Server state - used to track the server's current running state and health 
//     Server control - running and stopping the server 
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
 
#include <string.h>
#include <string>
#include <thread>
#include <memory>
#include <deque>
#include <mutex>
 
#include "Init.h"
#include "Logger.h"
#include "Server.h"
 
namespace ggk
{
// During initialization, we'll check for complation at this interval 
static const int kMaxAsyncInitCheckIntervalMS = 10;
 
// Our server thread 
static std::thread serverThread;
 
// The current server state 
volatile static GGKServerRunState serverRunState = EUninitialized;
 
// The current server health 
volatile static GGKServerHealth serverHealth = EOk;
 
// We store the old GLib print handler and error print handler so we can restore if 
static GPrintFunc printHandlerGLib;
static GPrintFunc printerrHandlerGLib;
static GLogFunc logHandlerGLib;
 
// Our update queue 
typedef std::tuple<std::string, std::string> QueueEntry;
std::deque<QueueEntry> updateQueue;
std::mutex updateQueueMutex;
 
// Internal method to set the run state of the server 
void setServerRunState(GGKServerRunState newState)
{
Logger::status(SSTR << "** SERVER RUN STATE CHANGED: " << ggkGetServerRunStateString(serverRunState) << " -> " << ggkGetServerRunStateString(newState));
serverRunState = newState;
}
 
// Internal method to set the health of the server 
void setServerHealth(GGKServerHealth newHealth)
{
Logger::status(SSTR << "** SERVER HEALTH CHANGED: " << ggkGetServerHealthString(serverHealth) << " -> " << ggkGetServerHealthString(newHealth));
serverHealth = newHealth;
}
}// namespace ggk 
 
using namespace ggk;
 
// --------------------------------------------------------------------------------------------------------------------------------- 
//  _                                  _     _             _   _ 
// | |    ___   __ _    _ __ ___  __ _(_)___| |_ _ __ __ _| |_(_) ___  _ ___ 
// | |   / _ \ / _` |  | '__/ _ \/ _` | / __| __| '__/ _` | __| |/ _ \| '_  | 
// | |__| (_) | (_| |  | | |  __/ (_| | \__ \ |_| | | (_| | |_| | (_) | | | | 
// |_____\___/ \__, |  |_|  \___|\__, |_|___/\__|_|  \__,_|\__|_|\___/|_| |_| 
//             |___/             |___/ 
// 
// --------------------------------------------------------------------------------------------------------------------------------- 
 
void ggkLogRegisterDebug(GGKLogReceiver receiver) { Logger::registerDebugReceiver(receiver); }
void ggkLogRegisterInfo(GGKLogReceiver receiver) { Logger::registerInfoReceiver(receiver); }
void ggkLogRegisterStatus(GGKLogReceiver receiver) { Logger::registerStatusReceiver(receiver); }
void ggkLogRegisterWarn(GGKLogReceiver receiver) { Logger::registerWarnReceiver(receiver); }
void ggkLogRegisterError(GGKLogReceiver receiver) { Logger::registerErrorReceiver(receiver); }
void ggkLogRegisterFatal(GGKLogReceiver receiver) { Logger::registerFatalReceiver(receiver); }
void ggkLogRegisterTrace(GGKLogReceiver receiver) { Logger::registerTraceReceiver(receiver); }
void ggkLogRegisterAlways(GGKLogReceiver receiver) { Logger::registerAlwaysReceiver(receiver); }
 
// --------------------------------------------------------------------------------------------------------------------------------- 
//  _   _           _       _                                                                                                     _ 
// | | | |_ __   __| | __ _| |_ ___     __ _ _   _  ___ _   _  ___    _ __ ___   __ _ _ __   __ _  __ _  ___ _ __ ___   ___ _ __ | |_ 
// | | | | '_ \ / _` |/ _` | __/ _ \   / _` | | | |/ _ \ | | |/ _ \  | '_ ` _ \ / _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __| 
// | |_| | |_) | (_| | (_| | ||  __/  | (_| | |_| |  __/ |_| |  __/  | | | | | | (_| | | | | (_| | (_| |  __/ | | | | |  __/ | | | |_ 
//  \___/| .__/ \__,_|\__,_|\__\___|   \__, |\__,_|\___|\__,_|\___|  |_| |_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__| 
//       |_|                              |_|                                                     |___/ 
// 
// Push/pop update notifications onto a queue. As these methods are where threads collide (i.e., this is how they communicate), 
// these methods are thread-safe. 
// --------------------------------------------------------------------------------------------------------------------------------- 
 
// Adds an update to the front of the queue for a characteristic at the given object path 
// 
// Returns non-zero value on success or 0 on failure. 
int ggkNofifyUpdatedCharacteristic(const char *pObjectPath)
{
return ggkPushUpdateQueue(pObjectPath, "org.bluez.GattCharacteristic1") != 0;
}
 
// Adds an update to the front of the queue for a descriptor at the given object path 
// 
// Returns non-zero value on success or 0 on failure. 
int ggkNofifyUpdatedDescriptor(const char *pObjectPath)
{
return ggkPushUpdateQueue(pObjectPath, "org.bluez.GattDescriptor1") != 0;
}
 
// Adds a named update to the front of the queue. Generally, this routine should not be used directly. Instead, use the 
// `ggkNofifyUpdatedCharacteristic()` instead. 
// 
// Returns non-zero value on success or 0 on failure. 
int ggkPushUpdateQueue(const char *pObjectPath, const char *pInterfaceName)
{
QueueEntry t(pObjectPath, pInterfaceName);
 
std::lock_guard<std::mutex> guard(updateQueueMutex);
updateQueue.push_front(t);
return 1;
}
 
// Get the next update from the back of the queue and returns the element in `element` as a string in the format: 
// 
//     "com/object/path|com.interface.name" 
// 
// If the queue is empty, this method returns `0` and does nothing. 
// 
// `elementLen` is the size of the `element` buffer in bytes. If the resulting string (including the null terminator) will not 
// fit within `elementLen` bytes, the method returns `-1` and does nothing. 
// 
// If `keep` is set to non-zero, the entry is not removed and will be retrieved again on the next call. Otherwise, the element 
// is removed. 
// 
// Returns 1 on success, 0 if the queue is empty, -1 on error (such as the length too small to store the element) 
int ggkPopUpdateQueue(char *pElementBuffer, int elementLen, int keep)
{
std::string result;
 
{
std::lock_guard<std::mutex> guard(updateQueueMutex);
 
// Check for an empty queue 
if (updateQueue.empty()) { return 0}
 
// Get the last element 
QueueEntry t = updateQueue.back();
 
// Get the result string 
result = std::get<0>(t) + "|" + std::get<1>(t);
 
// Ensure there's enough room for it 
if (result.length() + 1 > static_cast<size_t>(elementLen)) { return -1}
 
if (keep == 0)
{
updateQueue.pop_back();
}
}
 
// Copy the element string 
memcpy(pElementBuffer, result.c_str(), result.length() + 1);
 
return 1;
}
 
// Returns 1 if the queue is empty, otherwise 0 
int ggkUpdateQueueIsEmpty()
{
std::lock_guard<std::mutex> guard(updateQueueMutex);
return updateQueue.empty() ? 1 : 0;
}
 
// Returns the number of entries waiting in the queue 
int ggkUpdateQueueSize()
{
std::lock_guard<std::mutex> guard(updateQueueMutex);
return updateQueue.size();
}
 
// Removes all entries from the queue 
void ggkUpdateQueueClear()
{
std::lock_guard<std::mutex> guard(updateQueueMutex);
updateQueue.clear();
}
 
// --------------------------------------------------------------------------------------------------------------------------------- 
//  ____                     _        _ 
// |  _ \ _   _ _ __     ___| |_ __ _| |_ ___ 
// | |_) | | | | '_ \   / __| __/ _` | __/ _ ) 
// |  _ <| |_| | | | |  \__ \ || (_| | ||  __/ 
// |_| \_\\__,_|_| |_|  |___/\__\__,_|\__\___| 
// 
// Methods for maintaining and reporting the state of the server 
// --------------------------------------------------------------------------------------------------------------------------------- 
 
// Retrieve the current running state of the server 
// 
// See `GGKServerRunState` (enumeration) for more information. 
GGKServerRunState ggkGetServerRunState()
{
return serverRunState;
}
 
// Convert a `GGKServerRunState` into a human-readable string 
const char *ggkGetServerRunStateString(GGKServerRunState state)
{
switch(state)
{
case EUninitialized: return "Uninitialized";
case EInitializing: return "Initializing";
case ERunning: return "Running";
case EStopping: return "Stopping";
case EStopped: return "Stopped";
defaultreturn "Unknown";
}
}
 
// Convenience method to check ServerRunState for a running server 
int ggkIsServerRunning()
{
return serverRunState <= ERunning ? 1 : 0;
}
 
// --------------------------------------------------------------------------------------------------------------------------------- 
//  ____                              _                _ _   _ 
// / ___|  ___ _ ____   _____ _ __   | |__   ___  __ _| | |_| |___ 
// \___ \ / _ \ '__\ \ / / _ \ '__|  | '_ \ / _ \/ _` | | __| '_  | 
//  ___) |  __/ |   \ V /  __/ |     | | | |  __/ (_| | | |_| | | | 
// |____/ \___|_|    \_/ \___|_|     |_| |_|\___|\__,_|_|\__|_| |_| 
// 
// Methods for maintaining and reporting the health of the server 
// --------------------------------------------------------------------------------------------------------------------------------- 
 
// Retrieve the current health of the server 
// 
// See `GGKServerHealth` (enumeration) for more information. 
GGKServerHealth ggkGetServerHealth()
{
return serverHealth;
}
 
// Convert a `GGKServerHealth` into a human-readable string 
const char *ggkGetServerHealthString(GGKServerHealth state)
{
switch(state)
{
case EOk: return "Ok";
case EFailedInit: return "Failed initialization";
case EFailedRun: return "Failed run";
defaultreturn "Unknown";
}
}
 
// --------------------------------------------------------------------------------------------------------------------------------- 
//  ____  _                 _   _ 
// / ___|| |_ ___  _ __    | |_| |__   ___    ___  ___ _ ____   _____ _ __ 
// \___ \| __/ _ \| '_ \   | __| '_ \ / _ \  / __|/ _ \ '__\ \ / / _ \ '__| 
//  ___) | || (_) | |_) |  | |_| | | |  __/  \__ \  __/ |   \ V /  __/ | 
// |____/ \__\___/| .__/    \__|_| |_|\___|  |___/\___|_|    \_/ \___|_| 
//                |_| 
// 
// --------------------------------------------------------------------------------------------------------------------------------- 
 
// Tells the server to begin the shutdown process 
// 
// The shutdown process will interrupt any currently running asynchronous operation and prevent new operations from starting. 
// Once the server has stabilized, its event processing loop is terminated and the server is cleaned up. 
// 
// `ggkGetServerRunState` will return EStopped when shutdown is complete. To block until the shutdown is complete, see 
// `ggkWait()`. 
// 
// Alternatively, you can use `ggkShutdownAndWait()` to request the shutdown and block until the shutdown is complete. 
void ggkTriggerShutdown()
{
shutdown();
}
 
// Convenience method to trigger a shutdown (via `ggkTriggerShutdown()`) and also waits for shutdown to complete (via 
// `ggkWait()`) 
int ggkShutdownAndWait()
{
if (ggkIsServerRunning() != 0)
{
// Tell the server to shut down 
ggkTriggerShutdown();
}
 
// Block until it has shut down completely 
return ggkWait();
}
 
// --------------------------------------------------------------------------------------------------------------------------------- 
// __        __    _ _ 
// \ \      / /_ _(_) |_     ___  _ __     ___  ___ _ ____   _____ _ __ 
//  \ \ /\ / / _` | | __|   / _ \| '_ \   / __|/ _ \ '__\ \ / / _ \ '__| 
//   \ V  V / (_| | | |_   | (_) | | | |  \__ \  __/ |   \ V /  __/ | 
//    \_/\_/ \__,_|_|\__|   \___/|_| |_|  |___/\___|_|    \_/ \___|_| 
// 
// --------------------------------------------------------------------------------------------------------------------------------- 
 
// Blocks for up to maxAsyncInitTimeoutMS milliseconds until the server shuts down. 
// 
// If shutdown is successful, this method will return a non-zero value. Otherwise, it will return 0. 
// 
// If the server fails to stop for some reason, the thread will be killed. 
// 
// Typically, a call to this method would follow `ggkTriggerShutdown()`. 
int ggkWait()
{
int result = 0;
try
{
if (ggkGetServerRunState() <= ERunning)
{
Logger::info("Waiting for GGK server to stop");
}
 
if (serverThread.joinable())
{
serverThread.join();
}
 
result = 1;
}
catch(std::system_error &ex)
{
if (ex.code() == std::errc::invalid_argument)
{
Logger::warn(SSTR << "Server thread was not joinable during ggkWait(): " << ex.what());
}
else if (ex.code() == std::errc::no_such_process)
{
Logger::warn(SSTR << "Server thread was not valid during ggkWait(): " << ex.what());
}
else if (ex.code() == std::errc::resource_deadlock_would_occur)
{
Logger::warn(SSTR << "Deadlock avoided in call to ggkWait() (did the server thread try to stop itself?): " << ex.what());
}
else
{
Logger::warn(SSTR << "Unknown system_error code (" << ex.code() << ") during ggkWait(): " << ex.what());
}
}
 
// Restore the GLib output functions 
g_set_print_handler(printHandlerGLib);
g_set_printerr_handler(printerrHandlerGLib);
g_log_set_default_handler(logHandlerGLib, nullptr);
 
return result;
}
 
// --------------------------------------------------------------------------------------------------------------------------------- 
//  ____  _             _      _   _ 
// / ___|| |_ __ _ _ __| |_   | |_| |__   ___    ___  ___ _ ____   _____ _ __ 
// \___ \| __/ _` | '__| __|  | __| '_ \ / _ \  / __|/ _ \ '__\ \ / / _ \ '__| 
//  ___) | || (_| | |  | |_   | |_| | | |  __/  \__ \  __/ |   \ V /  __/ | 
// |____/ \__\__,_|_|   \__|   \__|_| |_|\___|  |___/\___|_|    \_/ \___|_| 
// 
// --------------------------------------------------------------------------------------------------------------------------------- 
 
// Set the server state to 'EInitializing' and then immediately create a server thread and initiate the server's async 
// processing on the server thread. 
// 
// At that point the current thread will block for maxAsyncInitTimeoutMS milliseconds or until initialization completes. 
// 
// If initialization was successful, the method will return a non-zero value with the server running on its own thread in 
// 'runServerThread'. 
// 
// If initialization was unsuccessful, this method will continue to block until the server has stopped. This method will then 
// return 0. 
// 
// IMPORTANT: 
// 
// The data setter uses void* types to allow receipt of unknown data types from the server. Ensure that you do not store these 
// pointers. Copy the data before returning from your getter delegate. 
// 
// Similarly, the pointer to data returned to the data getter should point to non-volatile memory so that the server can use it 
// safely for an indefinite period of time. 
// 
// pServiceName: The name of our server (collectino of services) 
// 
//     !!!IMPORTANT!!! 
// 
//     This name must match tha name configured in the D-Bus permissions. See the Readme.md file for more information. 
// 
//     This is used to build the path for our Bluetooth services. It also provides the base for the D-Bus owned name (see 
//     getOwnedName.) 
// 
//     This value will be stored as lower-case only. 
// 
//     Retrieve this value using the `getName()` method 
// 
// pAdvertisingName: The name for this controller, as advertised over LE 
// 
//     IMPORTANT: Setting the advertisingName will change the system-wide name of the device. If that's not what you want, set 
//     BOTH advertisingName and advertisingShortName to as empty string ("") to prevent setting the advertising 
//     name. 
// 
//     Retrieve this value using the `getAdvertisingName()` method 
// 
// pAdvertisingShortName: The short name for this controller, as advertised over LE 
// 
//     According to the spec, the short name is used in case the full name doesn't fit within Extended Inquiry Response (EIR) or 
//     Advertising Data (AD). 
// 
//     IMPORTANT: Setting the advertisingName will change the system-wide name of the device. If that's not what you want, set 
//     BOTH advertisingName and advertisingShortName to as empty string ("") to prevent setting the advertising 
//     name. 
// 
//     Retrieve this value using the `getAdvertisingShortName()` method 
// 
int ggkStart(const char *pServiceName, const char *pAdvertisingName, const char *pAdvertisingShortName, 
GGKServerDataGetter getter, GGKServerDataSetter setter, int maxAsyncInitTimeoutMS)
{
try
{
// 
// Start by capturing the GLib output 
// 
 
// Redirect GLib output to this log method 
printHandlerGLib = g_set_print_handler([](const gchar *string)
{
Logger::info(string);
});
printerrHandlerGLib = g_set_printerr_handler([](const gchar *string)
{
Logger::error(string);
});
logHandlerGLib = g_log_set_default_handler([](const gchar *log_domain, GLogLevelFlags log_levels, const gchar *message, gpointer /*user_data*/)
{
std::string str = std::string(log_domain) + "" + message;
if ((log_levels & (G_LOG_FLAG_RECURSION|G_LOG_FLAG_FATAL)) != 0)
{
Logger::fatal(str);
}
else if ((log_levels & (G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_ERROR)) != 0)
{
Logger::error(str);
}
else if ((log_levels & G_LOG_LEVEL_WARNING) != 0)
{
Logger::warn(str);
}
else if ((log_levels & G_LOG_LEVEL_DEBUG) != 0)
{
Logger::debug(str);
}
else
{
Logger::info(str);
}
}nullptr);
 
Logger::info(SSTR << "Starting GGK server '" << pAdvertisingName << "'");
 
// Allocate our server 
TheServer = std::make_shared<Server>(pServiceName, pAdvertisingName, pAdvertisingShortName, getter, setter);
 
// Start our server thread 
try
{
serverThread = std::thread(runServerThread);
}
catch(std::system_error &ex)
{
Logger::error(SSTR << "Server thread was unable to start (code " << ex.code() << ") during ggkStart(): " << ex.what());
 
setServerRunState(EStopped);
return 0;
}
 
// Waits for the server to pass the EInitializing state 
int retryTimeMS = 0;
while (retryTimeMS < maxAsyncInitTimeoutMS && ggkGetServerRunState() <= EInitializing)
{
std::this_thread::sleep_for(std::chrono::milliseconds(kMaxAsyncInitCheckIntervalMS));
retryTimeMS += kMaxAsyncInitCheckIntervalMS;
}
 
// If something went wrong, shut down 
if (retryTimeMS >= maxAsyncInitTimeoutMS)
{
Logger::error("GGK server initialization timed out");
 
setServerHealth(EFailedInit);
 
shutdown();
}
 
// If something went wrong, shut down if we've not already done so 
if (ggkGetServerRunState() != ERunning)
{
if (!ggkWait())
{
Logger::warn(SSTR << "Unable to stop the server after an error in ggkStart()");
}
 
return 0;
}
 
// Everything looks good 
Logger::trace("GGK server has started");
return 1;
}
catch(...)
{
Logger::error(SSTR << "Unknown exception during ggkStart()");
return 0;
}
}