// 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 .
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// >>
// >>> 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
#include
#include
#include
#include
#include
#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 QueueEntry;
std::deque 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 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 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(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 guard(updateQueueMutex);
return updateQueue.empty() ? 1 : 0;
}
// Returns the number of entries waiting in the queue
int ggkUpdateQueueSize()
{
std::lock_guard guard(updateQueueMutex);
return updateQueue.size();
}
// Removes all entries from the queue
void ggkUpdateQueueClear()
{
std::lock_guard 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";
default: return "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";
default: return "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(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;
}
}