// 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
// >>
//
// Herein lies the code that manages the full initialization (including the running) of the server
//
// >>
// >>> DISCUSSION
// >>
//
// This file contains the highest-level framework for our server:
//
// Initialization
// Adapter configuration (mode, settings, name, etc.)
// GATT server registration with BlueZ
// Signal handling (such as CTRL-C)
// Event management
// Graceful shutdown
//
// Want to poke around and see how things work? Here's a tip: Start at the bottom of the file and work upwards. It'll make a lot
// more sense, I promise.
//
// Want to become your own boss while working from home? (Just kidding.)
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#include
#include
#include
#include
#include
#include
#include "Server.h"
#include "Globals.h"
#include "Mgmt.h"
#include "HciAdapter.h"
#include "DBusObject.h"
#include "DBusInterface.h"
#include "GattCharacteristic.h"
#include "GattProperty.h"
#include "Logger.h"
#include "Init.h"
namespace ggk {
//
// Constants
//
static const int kPeriodicTimerFrequencySeconds = 1;
static const int kRetryDelaySeconds = 2;
static const int kIdleFrequencyMS = 10;
//
// Retries
//
static time_t retryTimeStart = 0;
//
// Adapter configuration
//
GDBusConnection *pBusConnection = nullptr;
static guint ownedNameId = 0;
static guint periodicTimeoutId = 0;
static std::vector registeredObjectIds;
static std::atomic pMainLoop(nullptr);
static GDBusObjectManager *pBluezObjectManager = nullptr;
static GDBusObject *pBluezAdapterObject = nullptr;
static GDBusObject *pBluezDeviceObject = nullptr;
static GDBusProxy *pBluezGattManagerProxy = nullptr;
static GDBusProxy *pBluezAdapterInterfaceProxy = nullptr;
static GDBusProxy *pBluezDeviceInterfaceProxy = nullptr;
static GDBusProxy *pBluezAdapterPropertiesInterfaceProxy = nullptr;
static bool bOwnedNameAcquired = false;
static bool bAdapterConfigured = false;
static bool bApplicationRegistered = false;
static std::string bluezGattManagerInterfaceName = "";
//
// Externs
//
extern void setServerRunState(enum GGKServerRunState newState);
extern void setServerHealth(enum GGKServerHealth newState);
//
// Forward declarations
//
static void initializationStateProcessor();
// ---------------------------------------------------------------------------------------------------------------------------------
// ___ _ _ __ _ _ _
// |_ _|__| | | ___ / / __| | __ _| |_ __ _ _ __ _ __ ___ ___ ___ ___ ___(_)_ __ __ _
// | |/ _` | |/ _ \ / / / _` |/ _` | __/ _` | | '_ \| '__/ _ \ / __/ _ \/ __/ __| | '_ \ / _` |
// | | (_| | | __/ / / | (_| | (_| | || (_| | | |_) | | | (_) | (_| __/\__ \__ \ | | | | (_| |
// |___\__,_|_|\___| /_/ \__,_|\__,_|\__\__,_| | .__/|_| \___/ \___\___||___/___/_|_| |_|\__, |
// |_| |___/
//
// Our idle funciton is what processes data updates. We handle this in a simple way. We update the data directly in our global
// `TheServer` object, then call `ggkPushUpdateQueue` to trigger that data to be updated (in whatever way the service responsible
// for that data() sees fit.
//
// This is done using the `ggkPushUpdateQueue` / `ggkPopUpdateQueue` methods to manage the queue of pending update messages. Each
// entry represents an interface that needs to be updated. The idleFunc calls the interface's `onUpdatedValue` method for each
// update.
//
// The idle processor will perform one update per idle tick, however, it will notify that there is more data so the idle ticks
// do not lag behind.
// ---------------------------------------------------------------------------------------------------------------------------------
// Our idle function
//
// This method is used to process data on the same thread as our main loop. This allows us to communicate with our service from
// the outside.
//
// IMPORTANT: This method must return 'true' if any work was performed, otherwise it must return 'false'. Returning 'true' will
// cause the idle loop to continue to call this method to process data at the maximum rate (which can peg the CPU at 100%.) By
// returning false when there is no work to do, we are nicer to the system.
bool idleFunc(void *pUserData)
{
// Don't do anything unless we're running
if (ggkGetServerRunState() != ERunning)
{
return false;
}
// Try to get an update
const int kQueueEntryLen = 1024;
char queueEntry[kQueueEntryLen];
if (ggkPopUpdateQueue(queueEntry, kQueueEntryLen, 0) != 1)
{
return false;
}
std::string entryString = queueEntry;
auto token = entryString.find('|');
if (token == std::string::npos)
{
Logger::error("Queue entry was not formatted properly - could not find separating token");
return false;
}
DBusObjectPath objectPath = DBusObjectPath(entryString.substr(0, token));
std::string interfaceName = entryString.substr(token+1);
// We have an update - call the onUpdatedValue method on the interface
std::shared_ptr pInterface = TheServer->findInterface(objectPath, interfaceName);
if (nullptr == pInterface)
{
Logger::warn(SSTR << "Unable to find interface for update: path[" << objectPath << "], name[" << interfaceName << "]");
}
else
{
// Is it a characteristic?
if (std::shared_ptr pCharacteristic = TRY_GET_CONST_INTERFACE_OF_TYPE(pInterface, GattCharacteristic))
{
Logger::debug(SSTR << "Processing updated value for interface '" << interfaceName << "' at path '" << objectPath << "'");
pCharacteristic->callOnUpdatedValue(pBusConnection, pUserData);
}
}
return false;
}
// ---------------------------------------------------------------------------------------------------------------------------------
// ____ _ _ _ _ _ _ _ _
// | _ \ ___(_)_ __ (_) |_(_) __ _| (_)______ _| |_(_) ___ _ ___
// | | | |/ _ \ | '_ \| | __| |/ _` | | |_ / _` | __| |/ _ \| '_ |
// | |_| | __/ | | | | | |_| | (_| | | |/ / (_| | |_| | (_) | | | |
// |____/ \___|_|_| |_|_|\__|_|\__,_|_|_/___\__,_|\__|_|\___/|_| |_|
//
//
// ---------------------------------------------------------------------------------------------------------------------------------
// Perform final cleanup of various resources that were allocated while the server was initialized and/or running
void uninit()
{
// We've left our main loop - nullify its pointer so we know we're no longer running
pMainLoop = nullptr;
if (nullptr != pBluezAdapterObject)
{
g_object_unref(pBluezAdapterObject);
pBluezAdapterObject = nullptr;
}
if (nullptr != pBluezDeviceObject)
{
g_object_unref(pBluezDeviceObject);
pBluezDeviceObject = nullptr;
}
if (nullptr != pBluezAdapterInterfaceProxy)
{
g_object_unref(pBluezAdapterInterfaceProxy);
pBluezAdapterInterfaceProxy = nullptr;
}
if (nullptr != pBluezDeviceInterfaceProxy)
{
g_object_unref(pBluezDeviceInterfaceProxy);
pBluezDeviceInterfaceProxy = nullptr;
}
if (nullptr != pBluezAdapterPropertiesInterfaceProxy)
{
g_object_unref(pBluezAdapterPropertiesInterfaceProxy);
pBluezAdapterPropertiesInterfaceProxy = nullptr;
}
if (nullptr != pBluezGattManagerProxy)
{
g_object_unref(pBluezGattManagerProxy);
pBluezGattManagerProxy = nullptr;
}
if (nullptr != pBluezObjectManager)
{
g_object_unref(pBluezObjectManager);
pBluezObjectManager = nullptr;
}
if (!registeredObjectIds.empty())
{
for (guint id : registeredObjectIds)
{
g_dbus_connection_unregister_object(pBusConnection, id);
}
registeredObjectIds.clear();
}
if (0 != periodicTimeoutId)
{
g_source_remove(periodicTimeoutId);
periodicTimeoutId = 0;
}
if (ownedNameId > 0)
{
g_bus_unown_name(ownedNameId);
}
if (nullptr != pBusConnection)
{
g_object_unref(pBusConnection);
pBusConnection = nullptr;
}
if (nullptr != pMainLoop)
{
g_main_loop_unref(pMainLoop);
pMainLoop = nullptr;
}
}
// ---------------------------------------------------------------------------------------------------------------------------------
// ____ _ _ _
// / ___|| |__ _ _| |_ __| | _____ ___ ___
// \___ \| '_ \| | | | __/ _` |/ _ \ \ /\ / / '_ |
// ___) | | | | |_| | || (_| | (_) \ V V /| | | |
// |____/|_| |_|\__,_|\__\__,_|\___/ \_/\_/ |_| |_|
//
// This is how we shutdown our server gracefully.
// ---------------------------------------------------------------------------------------------------------------------------------
// Trigger a graceful, asynchronous shutdown of the server
//
// This method is non-blocking and as such, will only trigger the shutdown process but not wait for it
void shutdown()
{
if (ggkGetServerRunState() >= EStopping)
{
Logger::warn(SSTR << "shutdown() called while already shutting down");
}
// Our new state: shutting down
setServerRunState(EStopping);
// Disconnect our HciAdapter
HciAdapter::getInstance().disconnect();
// If we still have a main loop, ask it to quit
if (nullptr != pMainLoop)
{
g_main_loop_quit(pMainLoop);
}
}
// ---------------------------------------------------------------------------------------------------------------------------------
// ____ _ _ _ _ _
// | _ \ ___ _ __(_) ___ __| (_) ___ | |_(_)_ __ ___ ___ _ __
// | |_) / _ \ '__| |/ _ \ / _` | |/ __| | __| | '_ ` _ \ / _ \ '__|
// | __/ __/ | | | (_) | (_| | | (__ | |_| | | | | | | __/ |
// |_| \___|_| |_|\___/ \__,_|_|\___| \__|_|_| |_| |_|\___|_|
//
// ---------------------------------------------------------------------------------------------------------------------------------
// Periodic timer handler
//
// A periodic timer is a timer fires every so often (see kPeriodicTimerFrequencySeconds.) This is used for our initialization
// failure retries, but custom code can also be added to a server description (see `onEvent()`)
gboolean onPeriodicTimer(gpointer pUserData)
{
// Deal with retry timers
if (0 != retryTimeStart)
{
Logger::debug(SSTR << "Ticking retry timer");
// Has the retry time expired?
int secondsRemaining = time(nullptr) - retryTimeStart - kRetryDelaySeconds;
if (secondsRemaining >= 0)
{
retryTimeStart = 0;
initializationStateProcessor();
}
}
// If we're registered, then go ahead and emit signals
if (bApplicationRegistered)
{
// Tick the object hierarchy
//
// The real goal here is to have the objects tick their interfaces (see `onEvent()` method when adding interfaces inside
// 'Server::Server()'
for (const DBusObject &object : TheServer->getObjects())
{
if (object.isPublished())
{
object.tickEvents(pBusConnection, pUserData);
}
}
}
return TRUE;
}
// ---------------------------------------------------------------------------------------------------------------------------------
// _____ _
// | ____|_ _____ _ __ | |_ ___
// | _| \ \ / / _ \ '_ \| __/ __|
// | |___ \ V / __/ | | | |_\__ )
// |_____| \_/ \___|_| |_|\__|___/
//
// Our event handlers. These are generic, as they parcel out the work to the appropriate server objects (see 'Server::Server()' for
// the code that manages event handlers.)
// ---------------------------------------------------------------------------------------------------------------------------------
// Handle D-Bus method calls
void onMethodCall
(
GDBusConnection *pConnection,
const gchar *pSender,
const gchar *pObjectPath,
const gchar *pInterfaceName,
const gchar *pMethodName,
GVariant *pParameters,
GDBusMethodInvocation *pInvocation,
gpointer pUserData
)
{
// Convert our input path into our custom type for path management
DBusObjectPath objectPath(pObjectPath);
if (!TheServer->callMethod(objectPath, pInterfaceName, pMethodName, pConnection, pParameters, pInvocation, pUserData))
{
Logger::error(SSTR << " + Method not found: [" << pSender << "]:[" << objectPath << "]:[" << pInterfaceName << "]:[" << pMethodName << "]");
g_dbus_method_invocation_return_dbus_error(pInvocation, kErrorNotImplemented.c_str(), "This method is not implemented");
return;
}
return;
}
// Handle D-Bus requests to get a property
GVariant *onGetProperty
(
GDBusConnection *pConnection,
const gchar *pSender,
const gchar *pObjectPath,
const gchar *pInterfaceName,
const gchar *pPropertyName,
GError **ppError,
gpointer pUserData
)
{
// Convert our input path into our custom type for path management
DBusObjectPath objectPath(pObjectPath);
const GattProperty *pProperty = TheServer->findProperty(objectPath, pInterfaceName, pPropertyName);
std::string propertyPath = std::string("[") + pSender + "]:[" + objectPath.toString() + "]:[" + pInterfaceName + "]:[" + pPropertyName + "]";
if (!pProperty)
{
Logger::error(SSTR << "Property(get) not found: " << propertyPath);
g_set_error(ppError, G_IO_ERROR, G_IO_ERROR_FAILED, ("Property(get) not found: " + propertyPath).c_str(), pSender);
return nullptr;
}
if (!pProperty->getGetterFunc())
{
Logger::error(SSTR << "Property(get) func not found: " << propertyPath);
g_set_error(ppError, G_IO_ERROR, G_IO_ERROR_FAILED, ("Property(get) func not found: " + propertyPath).c_str(), pSender);
return nullptr;
}
Logger::info(SSTR << "Calling property getter: " << propertyPath);
GVariant *pResult = pProperty->getGetterFunc()(pConnection, pSender, objectPath.c_str(), pInterfaceName, pPropertyName, ppError, pUserData);
if (nullptr == pResult)
{
g_set_error(ppError, G_IO_ERROR, G_IO_ERROR_FAILED, ("Property(get) failed: " + propertyPath).c_str(), pSender);
return nullptr;
}
return pResult;
}
// Handle D-Bus requests to set a property
gboolean onSetProperty
(
GDBusConnection *pConnection,
const gchar *pSender,
const gchar *pObjectPath,
const gchar *pInterfaceName,
const gchar *pPropertyName,
GVariant *pValue,
GError **ppError,
gpointer pUserData
)
{
// Convert our input path into our custom type for path management
DBusObjectPath objectPath(pObjectPath);
const GattProperty *pProperty = TheServer->findProperty(objectPath, pInterfaceName, pPropertyName);
std::string propertyPath = std::string("[") + pSender + "]:[" + objectPath.toString() + "]:[" + pInterfaceName + "]:[" + pPropertyName + "]";
if (!pProperty)
{
Logger::error(SSTR << "Property(set) not found: " << propertyPath);
g_set_error(ppError, G_IO_ERROR, G_IO_ERROR_FAILED, ("Property(set) not found: " + propertyPath).c_str(), pSender);
return false;
}
if (!pProperty->getSetterFunc())
{
Logger::error(SSTR << "Property(set) func not found: " << propertyPath);
g_set_error(ppError, G_IO_ERROR, G_IO_ERROR_FAILED, ("Property(set) func not found: " + propertyPath).c_str(), pSender);
return false;
}
Logger::info(SSTR << "Calling property getter: " << propertyPath);
if (!pProperty->getSetterFunc()(pConnection, pSender, objectPath.c_str(), pInterfaceName, pPropertyName, pValue, ppError, pUserData))
{
g_set_error(ppError, G_IO_ERROR, G_IO_ERROR_FAILED, ("Property(set) failed: " + propertyPath).c_str(), pSender);
return false;
}
return true;
}
// ---------------------------------------------------------------------------------------------------------------------------------
// _____ _ _ _
// | ___|_ _(_) |_ _ _ __ ___ _ __ ___ __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_
// | |_ / _` | | | | | | '__/ _ \ | '_ ` _ \ / _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __|
// | _| (_| | | | |_| | | | __/ | | | | | | (_| | | | | (_| | (_| | __/ | | | | | __/ | | | |_
// |_| \__,_|_|_|\__,_|_| \___| |_| |_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__|
// |___/
//
// ---------------------------------------------------------------------------------------------------------------------------------
// Convenience method for setting a retry timer so that operations can be continuously retried until we eventually succeed
void setRetry()
{
retryTimeStart = time(nullptr);
}
// Convenience method for setting a retry timer so that failures (related to initialization) can be continuously retried until we
// eventually succeed.
void setRetryFailure()
{
setRetry();
Logger::warn(SSTR << " + Will retry the failed operation in about " << kRetryDelaySeconds << " seconds");
}
// ---------------------------------------------------------------------------------------------------------------------------------
// ____ _ _____ _____ _ _ _ _
// / ___| / \|_ _|_ _| _ __ ___ __ _(_)___| |_ _ __ __ _| |_(_) ___ _ ___
// | | _ / _ \ | | | | | '__/ _ \/ _` | / __| __| '__/ _` | __| |/ _ \| '_ |
// | |_| |/ ___ \| | | | | | | __/ (_| | \__ \ |_| | | (_| | |_| | (_) | | | |
// \____/_/ \_\_| |_| |_| \___|\__, |_|___/\__|_| \__,_|\__|_|\___/|_| |_|
// |___/
//
// ---------------------------------------------------------------------------------------------------------------------------------
// Use the BlueZ GATT Manager proxy to register our GATT application with BlueZ
void doRegisterApplication()
{
g_auto(GVariantBuilder) builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
GVariant *pParams = g_variant_new("(oa{sv})", "/", &builder);
g_dbus_proxy_call
(
pBluezGattManagerProxy, // GDBusProxy *proxy
"RegisterApplication", // const gchar *method_name (ex: "GetManagedObjects")
pParams, // GVariant *parameters
G_DBUS_CALL_FLAGS_NONE, // GDBusCallFlags flags
-1, // gint timeout_msec
nullptr, // GCancellable *cancellable
// GAsyncReadyCallback callback
[] (GObject * /*pSourceObject*/, GAsyncResult *pAsyncResult, gpointer /*pUserData*/)
{
GError *pError = nullptr;
GVariant *pVariant = g_dbus_proxy_call_finish(pBluezGattManagerProxy, pAsyncResult, &pError);
if (nullptr == pVariant)
{
Logger::error(SSTR << "Failed to register application: " << (nullptr == pError ? "Unknown" : pError->message));
setRetryFailure();
}
else
{
g_variant_unref(pVariant);
Logger::debug(SSTR << "GATT application registered with BlueZ");
bApplicationRegistered = true;
}
// Keep going...
initializationStateProcessor();
},
nullptr // gpointer user_data
);
}
// ---------------------------------------------------------------------------------------------------------------------------------
// ___ _ _ _ _ _ _ _
// / _ \| |__ (_) ___ ___| |_ _ __ ___ __ _(_)___| |_ _ __ __ _| |_(_) ___ _ ___
// | | | | '_ \| |/ _ \/ __| __| | '__/ _ \/ _` | / __| __| '__/ _` | __| |/ _ \| '_ |
// | |_| | |_) | | __/ (__| |_ | | | __/ (_| | \__ \ |_| | | (_| | |_| | (_) | | | |
// \___/|_.__// |\___|\___|\__| |_| \___|\__, |_|___/\__|_| \__,_|\__|_|\___/|_| |_|
// |__/ |___/
//
// Before we can register our service(s) with BlueZ, we must first register ourselves with D-Bus. The easiest way to do this is to
// use an XML description of our D-Bus objects.
// ---------------------------------------------------------------------------------------------------------------------------------
void registerNodeHierarchy(GDBusNodeInfo *pNode, const DBusObjectPath &basePath = DBusObjectPath(), int depth = 1)
{
std::string prefix;
prefix.insert(0, depth * 2, ' ');
static GDBusInterfaceVTable interfaceVtable;
interfaceVtable.method_call = onMethodCall;
interfaceVtable.get_property = onGetProperty;
interfaceVtable.set_property = onSetProperty;
GDBusInterfaceInfo **ppInterface = pNode->interfaces;
Logger::debug(SSTR << prefix << "+ " << pNode->path);
while(nullptr != *ppInterface)
{
GError *pError = nullptr;
Logger::debug(SSTR << prefix << " (iface: " << (*ppInterface)->name << ")");
guint registeredObjectId = g_dbus_connection_register_object
(
pBusConnection, // GDBusConnection *connection
basePath.c_str(), // const gchar *object_path
*ppInterface, // GDBusInterfaceInfo *interface_info
&interfaceVtable, // const GDBusInterfaceVTable *vtable
nullptr, // gpointer user_data
nullptr, // GDestroyNotify user_data_free_func
&pError // GError **error
);
if (0 == registeredObjectId)
{
Logger::error(SSTR << "Failed to register object: " << (nullptr == pError ? "Unknown" : pError->message));
// Cleanup and pretend like we were never here
g_dbus_node_info_unref(pNode);
registeredObjectIds.clear();
// Try again later
setRetryFailure();
return;
}
// Save the registered object Id so we can clean it up later
registeredObjectIds.push_back(registeredObjectId);
++ppInterface;
}
GDBusNodeInfo **ppChild = pNode->nodes;
while(nullptr != *ppChild)
{
registerNodeHierarchy(*ppChild, basePath + (*ppChild)->path, depth + 1);
++ppChild;
}
}
void registerObjects()
{
// Parse each object into an XML interface tree
for (const DBusObject &object : TheServer->getObjects())
{
GError *pError = nullptr;
std::string xmlString = object.generateIntrospectionXML();
GDBusNodeInfo *pNode = g_dbus_node_info_new_for_xml(xmlString.c_str(), &pError);
if (nullptr == pNode)
{
Logger::error(SSTR << "Failed to introspect XML: " << (nullptr == pError ? "Unknown" : pError->message));
setRetryFailure();
return;
}
Logger::debug(SSTR << "Registering object hierarchy with D-Bus hierarchy");
// Register the node hierarchy
registerNodeHierarchy(pNode, DBusObjectPath(pNode->path));
// Cleanup the node
g_dbus_node_info_unref(pNode);
}
// Keep going
initializationStateProcessor();
}
// ---------------------------------------------------------------------------------------------------------------------------------
// _ _ _ __ _ _ _
// / \ __| | __ _ _ __ | |_ ___ _ __ ___ ___ _ __ / _(_) __ _ _ _ _ __ __ _| |_(_) ___ _ ___
// / _ \ / _` |/ _` | '_ \| __/ _ \ '__| / __/ _ \| '_ \| |_| |/ _` | | | | '__/ _` | __| |/ _ \| '_ |
// / ___ \ (_| | (_| | |_) | || __/ | | (_| (_) | | | | _| | (_| | |_| | | | (_| | |_| | (_) | | | |
// /_/ \_\__,_|\__,_| .__/ \__\___|_| \___\___/|_| |_|_| |_|\__, |\__,_|_| \__,_|\__|_|\___/|_| |_|
// |_| |___/
//
// ---------------------------------------------------------------------------------------------------------------------------------
// Configure an adapter to ensure it is setup the way we need. We turn things on that we need and turn everything else off
// (to maximize security.)
//
// Note that this only works for the first adapter (index 0). Search for kControllerIndex for information.
//
// See also: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/mgmt-api.txt
void configureAdapter()
{
Mgmt mgmt;
// Get our properly truncated advertising names
std::string advertisingName = Mgmt::truncateName(TheServer->getAdvertisingName());
std::string advertisingShortName = Mgmt::truncateShortName(TheServer->getAdvertisingShortName());
// Find out what our current settings are
HciAdapter::ControllerInformation info = HciAdapter::getInstance().getControllerInformation();
// Are all of our settings the way we want them?
bool pwFlag = info.currentSettings.isSet(HciAdapter::EHciPowered) == true;
bool leFlag = info.currentSettings.isSet(HciAdapter::EHciLowEnergy) == true;
bool brFlag = info.currentSettings.isSet(HciAdapter::EHciBasicRate_EnhancedDataRate) == TheServer->getEnableBREDR();
bool scFlag = info.currentSettings.isSet(HciAdapter::EHciSecureConnections) == TheServer->getEnableSecureConnection();
bool bnFlag = info.currentSettings.isSet(HciAdapter::EHciBondable) == TheServer->getEnableBondable();
bool cnFlag = info.currentSettings.isSet(HciAdapter::EHciConnectable) == TheServer->getEnableConnectable();
bool adFlag = info.currentSettings.isSet(HciAdapter::EHciAdvertising) == TheServer->getEnableAdvertising();
bool anFlag = (advertisingName.length() == 0 || advertisingName == info.name) && (advertisingShortName.length() == 0 || advertisingShortName == info.shortName);
// If everything is setup already, we're done
if (pwFlag && leFlag && brFlag && scFlag && bnFlag && cnFlag && adFlag && anFlag)
{
Logger::info("The adapter is fully configured");
// We're all set, nothing to do!
bAdapterConfigured = true;
initializationStateProcessor();
return;
}
// We need it off to start with
if (pwFlag)
{
Logger::debug("Powering off");
mgmt.setPowered(false);
}
// Enable the LE state (we always set this state if it's not set)
if (!leFlag)
{
Logger::debug("Enabling LE");
mgmt.setLE(true);
}
// Change the Br/Edr state?
//
// Note that enabling this requries LE to already be enabled or this command will receive a 'rejected' result
if (!brFlag)
{
Logger::debug(SSTR << (TheServer->getEnableBREDR() ? "Enabling":"Disabling") << " BR/EDR");
mgmt.setBredr(TheServer->getEnableBREDR());
}
// Change the Secure Connectinos state?
if (!scFlag)
{
Logger::debug(SSTR << (TheServer->getEnableSecureConnection() ? "Enabling":"Disabling") << " Secure Connections");
mgmt.setSecureConnections(TheServer->getEnableSecureConnection() ? 1 : 0);
}
// Change the Bondable state?
if (!bnFlag)
{
Logger::debug(SSTR << (TheServer->getEnableBondable() ? "Enabling":"Disabling") << " Bondable");
mgmt.setBondable(TheServer->getEnableBondable());
}
// Change the Connectable state?
if (!cnFlag)
{
Logger::debug(SSTR << (TheServer->getEnableConnectable() ? "Enabling":"Disabling") << " Connectable");
mgmt.setConnectable(TheServer->getEnableConnectable());
}
// Change the Advertising state?
if (!adFlag)
{
Logger::debug(SSTR << (TheServer->getEnableAdvertising() ? "Enabling":"Disabling") << " Advertising");
mgmt.setAdvertising(TheServer->getEnableAdvertising() ? 1 : 0);
}
// Set the name?
if (!anFlag)
{
Logger::info(SSTR << "Setting advertising name to '" << advertisingName << "' (with short name: '" << advertisingShortName << "')");
mgmt.setName(advertisingName.c_str(), advertisingShortName.c_str());
}
// Turn it back on
Logger::debug("Powering on");
mgmt.setPowered(true);
// Give it some extra time to power up - this shouldn't really be necessary, but it won't hurt
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// We can ignore errors on this - we're just letting it dump the output
HciAdapter::getInstance().getControllerInformation();
// We always set the retry (silently) so we can validate our settings once the adapter has had a chance to respond to all
// of our requests
setRetry();
}
// ---------------------------------------------------------------------------------------------------------------------------------
// _ _ _
// / \ __| | __ _ _ __ | |_ ___ _ __
// / _ \ / _` |/ _` | '_ \| __/ _ \ '__|
// / ___ \ (_| | (_| | |_) | || __/ |
// /_/ \_\__,_|\__,_| .__/ \__\___|_|
// |_|
//
// ---------------------------------------------------------------------------------------------------------------------------------
// Find the BlueZ's GATT Manager interface for the *first* Bluetooth adapter provided by BlueZ. We'll need this to register our
// GATT server with BlueZ.
void findAdapterInterface()
{
// Get a list of the BlueZ's D-Bus objects
GList *pObjects = g_dbus_object_manager_get_objects(pBluezObjectManager);
if (nullptr == pObjects)
{
Logger::error(SSTR << "Unable to get ObjectManager objects");
setRetryFailure();
return;
}
// Scan the list of objects we find one with a GATT manager interface
//
// Note that if there are multiple interfaces, we will only find the first
for (guint i = 0; i < g_list_length(pObjects) && bluezGattManagerInterfaceName.empty(); ++i)
{
// Current object in question
pBluezAdapterObject = static_cast(g_list_nth_data(pObjects, i));
if (nullptr == pBluezAdapterObject) { continue; }
// See if it has a GATT manager interface
pBluezGattManagerProxy = reinterpret_cast(g_dbus_object_get_interface(pBluezAdapterObject, "org.bluez.GattManager1"));
if (nullptr == pBluezGattManagerProxy) { continue; }
// Get the interface proxy for this adapter - this will come in handy later
pBluezAdapterInterfaceProxy = reinterpret_cast(g_dbus_object_get_interface(pBluezAdapterObject, "org.bluez.Adapter1"));
if (nullptr == pBluezAdapterInterfaceProxy)
{
Logger::warn(SSTR << "Failed to get adapter proxy for interface 'org.bluez.Adapter1'");
continue;
}
// Get the interface proxy for this adapter's properties - this will come in handy later
pBluezAdapterPropertiesInterfaceProxy = reinterpret_cast(g_dbus_object_get_interface(pBluezAdapterObject, "org.freedesktop.DBus.Properties"));
if (nullptr == pBluezAdapterPropertiesInterfaceProxy)
{
Logger::warn(SSTR << "Failed to get adapter properties proxy for interface 'org.freedesktop.DBus.Properties'");
continue;
}
// Finally, save off the interface name, we're done!
bluezGattManagerInterfaceName = g_dbus_proxy_get_object_path(pBluezGattManagerProxy);
break;
}
// Get a fresh copy of our objects so we can release the entire list
pBluezAdapterObject = g_dbus_object_manager_get_object(pBluezObjectManager, g_dbus_object_get_object_path(pBluezAdapterObject));
// We'll need access to the device object so we can set properties on it
pBluezDeviceObject = g_dbus_object_manager_get_object(pBluezObjectManager, g_dbus_object_get_object_path(pBluezAdapterObject));
// Cleanup the list
for (guint i = 0; i < g_list_length(pObjects) && bluezGattManagerInterfaceName.empty(); ++i)
{
g_object_unref(g_list_nth_data(pObjects, i));
}
g_list_free(pObjects);
// If we didn't find the adapter object, reset things and we'll try again later
if (nullptr == pBluezAdapterObject || nullptr == pBluezDeviceObject)
{
Logger::warn(SSTR << "Unable to find BlueZ objects outside of object list");
bluezGattManagerInterfaceName.clear();
}
// If we never ended up with an interface name, bail now
if (bluezGattManagerInterfaceName.empty())
{
Logger::error(SSTR << "Unable to find the adapter");
setRetryFailure();
return;
}
// Keep going
initializationStateProcessor();
}
// ---------------------------------------------------------------------------------------------------------------------------------
// ____ _ _____ ___ _ _ _ __ __
// | __ )| |_ _ ___|__ / / _ \| |__ (_) ___ ___| |_| \/ | __ _ _ __ __ _ __ _ ___ _ __
// | _ \| | | | |/ _ \ / / | | | | '_ \| |/ _ \/ __| __| |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '__|
// | |_) | | |_| | __// /_ | |_| | |_) | | __/ (__| |_| | | | (_| | | | | (_| | (_| | __/ |
// |____/|_|\__,_|\___/____| \___/|_.__// |\___|\___|\__|_| |_|\__,_|_| |_|\__,_|\__, |\___|_|
// |__/ |___/
//
// ---------------------------------------------------------------------------------------------------------------------------------
// Get the D-Bus Object Manager client to the BlueZ ObjectManager object
//
// An ObjectManager allows us to find out what objects (and from those, interfaces, etc.) are available from an owned name. We'll
// use this to interrogate BlueZ's objects to find an adapter we can use, among other things.
void getBluezObjectManager()
{
g_dbus_object_manager_client_new
(
pBusConnection, // GDBusConnection
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, // GDBusObjectManagerClientFlags
"org.bluez", // Owner name (or well-known name)
"/", // Object path
nullptr, // GDBusProxyTypeFunc get_proxy_type_func
nullptr, // gpointer get_proxy_type_user_data
nullptr, // GDestroyNotify get_proxy_type_destroy_notify
nullptr, // GCancellable *cancellable
// GAsyncReadyCallback callback
[] (GObject * /*pSourceObject*/, GAsyncResult *pAsyncResult, gpointer /*pUserData*/)
{
// Store BlueZ's ObjectManager
GError *pError = nullptr;
pBluezObjectManager = g_dbus_object_manager_client_new_finish(pAsyncResult, &pError);
if (nullptr == pBluezObjectManager)
{
Logger::error(SSTR << "Failed to get an ObjectManager client: " << (nullptr == pError ? "Unknown" : pError->message));
setRetryFailure();
return;
}
// Keep going
initializationStateProcessor();
},
nullptr // gpointer user_data
);
}
// ---------------------------------------------------------------------------------------------------------------------------------
// ___ _
// / _ \__ ___ __ ___ __| | _ __ __ _ _ __ ___ ___
// | | | \ \ /\ / / '_ \ / _ \/ _` | | '_ \ / _` | '_ ` _ \ / _ )
// | |_| |\ V V /| | | | __/ (_| | | | | | (_| | | | | | | __/
// \___/ \_/\_/ |_| |_|\___|\__,_| |_| |_|\__,_|_| |_| |_|\___|
//
// ---------------------------------------------------------------------------------------------------------------------------------
// Acquire an "owned name" with D-Bus. This name represents our server as a whole, identifying us on D-Bus and allowing others
// (BlueZ) to communicate back to us. All of the D-Bus objects (which represent our BlueZ services, characteristics, etc.) will all
// reside under this owned name.
//
// Note about error management: We don't yet hwave a timeout callback running for retries; errors are considered fatal
void doOwnedNameAcquire()
{
// Our name is not presently lost
bOwnedNameAcquired = false;
ownedNameId = g_bus_own_name_on_connection
(
pBusConnection, // GDBusConnection *connection
TheServer->getOwnedName().c_str(), // const gchar *name
G_BUS_NAME_OWNER_FLAGS_NONE, // GBusNameOwnerFlags flags
// GBusNameAcquiredCallback name_acquired_handler
[](GDBusConnection *, const gchar *, gpointer)
{
// Handy way to get periodic activity
periodicTimeoutId = g_timeout_add_seconds(kPeriodicTimerFrequencySeconds, onPeriodicTimer, pBusConnection);
if (periodicTimeoutId <= 0)
{
Logger::fatal(SSTR << "Failed to add a periodic timer");
setServerHealth(EFailedInit);
shutdown();
}
// Bus name acquired
bOwnedNameAcquired = true;
// Keep going...
initializationStateProcessor();
},
// GBusNameLostCallback name_lost_handler
[](GDBusConnection *, const gchar *, gpointer)
{
// Bus name lost
bOwnedNameAcquired = false;
// If we don't have a periodicTimeout (which we use for error recovery) then we're sunk
if (0 == periodicTimeoutId)
{
Logger::fatal(SSTR << "Unable to acquire an owned name ('" << TheServer->getOwnedName() << "') on the bus");
setServerHealth(EFailedInit);
shutdown();
}
else
{
Logger::warn(SSTR << "Owned name ('" << TheServer->getOwnedName() << "') lost");
setRetryFailure();
return;
}
// Keep going...
initializationStateProcessor();
},
nullptr, // gpointer user_data
nullptr // GDestroyNotify user_data_free_func
);
}
// ---------------------------------------------------------------------------------------------------------------------------------
// ____
// | __ ) _ _ ___
// | _ \| | | / __|
// | |_) | |_| \__ )
// |____/ \__,_|___/
//
// ---------------------------------------------------------------------------------------------------------------------------------
// Acquire a connection to the SYSTEM bus so we can communicate with BlueZ.
//
// Note about error management: We don't yet hwave a timeout callback running for retries; errors are considered fatal
void doBusAcquire()
{
// Acquire a connection to the SYSTEM bus
g_bus_get
(
G_BUS_TYPE_SYSTEM, // GBusType bus_type
nullptr, // GCancellable *cancellable
// GAsyncReadyCallback callback
[] (GObject */*pSourceObject*/, GAsyncResult *pAsyncResult, gpointer /*pUserData*/)
{
GError *pError = nullptr;
pBusConnection = g_bus_get_finish(pAsyncResult, &pError);
if (nullptr == pBusConnection)
{
Logger::fatal(SSTR << "Failed to get bus connection: " << (nullptr == pError ? "Unknown" : pError->message));
setServerHealth(EFailedInit);
shutdown();
}
// Continue
initializationStateProcessor();
},
nullptr // gpointer user_data
);
}
// ---------------------------------------------------------------------------------------------------------------------------------
// ____ _ _ _
// / ___|| |_ __ _| |_ ___ _ __ ___ __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_
// \___ \| __/ _` | __/ _ \ | '_ ` _ \ / _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __|
// ___) | || (_| | || __/ | | | | | | (_| | | | | (_| | (_| | __/ | | | | | __/ | | | |_
// |____/ \__\__,_|\__\___| |_| |_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__|
// |___/
//
// ---------------------------------------------------------------------------------------------------------------------------------
// Poor-man's state machine, which effectively ensures everything is initialized in order by verifying actual initialization state
// rather than stepping through a set of numeric states. This way, if something fails in an out-of-order sort of way, we can still
// handle it and recover nicely.
void initializationStateProcessor()
{
// If we're in our end-of-life or waiting for a retry, don't process states
if (ggkGetServerRunState() > ERunning || 0 != retryTimeStart) { return; }
//
// Get a bus connection
//
if (nullptr == pBusConnection)
{
Logger::debug(SSTR << "Acquiring bus connection");
doBusAcquire();
return;
}
//
// Acquire an owned name on the bus
//
if (!bOwnedNameAcquired)
{
Logger::debug(SSTR << "Acquiring owned name: '" << TheServer->getOwnedName() << "'");
doOwnedNameAcquire();
return;
}
//
// Get BlueZ's ObjectManager
//
if (nullptr == pBluezObjectManager)
{
Logger::debug(SSTR << "Getting BlueZ ObjectManager");
getBluezObjectManager();
return;
}
//
// Find the adapter interface
//
if (bluezGattManagerInterfaceName.empty())
{
Logger::debug(SSTR << "Finding BlueZ GattManager1 interface");
findAdapterInterface();
return;
}
//
// Find the adapter interface
//
if (!bAdapterConfigured)
{
Logger::debug(SSTR << "Configuring BlueZ adapter '" << bluezGattManagerInterfaceName << "'");
configureAdapter();
return;
}
//
// Register our object with D-bus
//
if (registeredObjectIds.empty())
{
Logger::debug(SSTR << "Registering with D-Bus");
registerObjects();
return;
}
// Register our appliation with the BlueZ GATT manager
if (!bApplicationRegistered)
{
Logger::debug(SSTR << "Registering application with BlueZ GATT manager");
doRegisterApplication();
return;
}
// At this point, we should be fully initialized
//
// It shouldn't ever happen, but just in case, let's double-check that we're healthy and if not, shutdown immediately
if (ggkGetServerHealth() != EOk)
{
shutdown();
return;
}
// Successful initialization - switch to running state
setServerRunState(ERunning);
}
// ---------------------------------------------------------------------------------------------------------------------------------
// ____ _
// | _ \ _ _ _ __ ___ ___ _ ____ _____ _ __ _ __ _ _ _ __ | |
// | |_) | | | | '_ \ / __|/ _ \ '__\ \ / / _ \ '__| | '__| | | | '_ \| |
// | _ <| |_| | | | | \__ \ __/ | \ V / __/ | _ | | | |_| | | | |_|
// |_| \_\\__,_|_| |_| |___/\___|_| \_/ \___|_|( ) |_| \__,_|_| |_(_)
// |/
//
// ---------------------------------------------------------------------------------------------------------------------------------
// Entry point for the asynchronous server thread
//
// This method should not be called directly, instead, direct your attention over to `ggkStart()`
void runServerThread()
{
// Set the initialization state
setServerRunState(EInitializing);
// Start our state processor, which is really just a simplified state machine that steps us through an asynchronous
// initialization process.
//
// In case you're wondering if these really need to be async, the answer is yes. For one, it's the right way to do it. But
// the more practical response is that the main loop must be running (see below) in order for us to receive and respond to
// events from BlueZ. Well, one of the calls we need to make during initialization is 'RegisterApplication'. This method will
// will require that we respond the 'GetNamedObjects' method before it returns. If we were to call the synchronous version of
// 'RegisterApplication', then we've effectively created a deadlock.
//
// There are alternatives, but using async methods is the recommended way.
initializationStateProcessor();
Logger::debug(SSTR << "Starting GLib main loop");
pMainLoop = g_main_loop_new(NULL, FALSE);
// Add the idle function
//
// Note that we actually run the idle function from a lambda. This allows us to manage the inter-idle sleep so we don't
// soak up 100% of our CPU.
guint res = g_idle_add
(
[](gpointer pUserData) -> gboolean
{
// Try to process some data and if no data is processed, sleep for the requested frequency
if (!idleFunc(pUserData))
{
std::this_thread::sleep_for(std::chrono::milliseconds(kIdleFrequencyMS));
}
// Always return TRUE so our idle remains in tact
return TRUE;
},
nullptr
);
if (res == 0)
{
Logger::error(SSTR << "Unable to add idle to main loop");
}
g_main_loop_run(pMainLoop);
// We have stopped
setServerRunState(EStopped);
Logger::info("GGK server stopped");
// Cleanup
uninit();
}
}; // namespace ggk